Skip to content
Merged
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
15 changes: 11 additions & 4 deletions lib/rspec/openapi/schema_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ def build(record)

private

def enrich_with_required_keys(obj)
obj[:required] = obj[:properties]&.keys
obj
end

def response_example(record, disposition:)
return nil if !example_enabled? || disposition

Expand Down Expand Up @@ -114,29 +119,31 @@ def build_request_body(record)
{
content: {
normalize_content_type(record.request_content_type) => {
schema: build_property(record.request_params),
schema: build_property(record.request_params, set_required: true),
example: (build_example(record.request_params) if example_enabled?),
}.compact,
},
}
end

def build_property(value, disposition: nil)
# TODO: Remove set_required when implementing "required" to all places
def build_property(value, disposition: nil, set_required: false)
property = build_type(value, disposition)

case value
when Array
if value.empty?
property[:items] = {} # unknown
else
property[:items] = build_property(value.first)
property[:items] = build_property(value.first, set_required: set_required)
end
when Hash
property[:properties] = {}.tap do |properties|
value.each do |key, v|
properties[key] = build_property(v)
properties[key] = build_property(v, set_required: set_required)
end
end
property = enrich_with_required_keys(property) if set_required
end
property
end
Expand Down
4 changes: 4 additions & 0 deletions lib/rspec/openapi/schema_merger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ def merge_schema!(base, spec)
if key == 'parameters'
base[key] = value | base[key]
base[key].uniq! { |param| param.slice('name', 'in') }
elsif key == 'required'
# Preserve properties that appears in all test cases
base[key] = value & base[key]
else
# last one wins
base[key] = value
end
else
Expand Down
2 changes: 1 addition & 1 deletion spec/rails/app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ def create
name: params[:name],
relations: {
avatar: {
url: params[:avatar_url],
url: params[:avatar_url] || 'https://example.com/avatar.png',
},
pets: params[:pets] || [],
},
Expand Down
23 changes: 19 additions & 4 deletions spec/rails/doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,12 @@
"database_id": {
"type": "integer"
}
}
},
"required": [
"name",
"description",
"database_id"
]
},
"example": {
"name": "k0kubun",
Expand Down Expand Up @@ -407,9 +412,16 @@
"caption": {
"type": "string"
}
}
},
"required": [
"image",
"caption"
]
}
}
},
"required": [
"nested"
]
},
"example": {
"nested": {
Expand Down Expand Up @@ -569,7 +581,10 @@
"no_content": {
"type": "string"
}
}
},
"required": [
"no_content"
]
},
"example": {
"no_content": "true"
Expand Down
31 changes: 25 additions & 6 deletions spec/rails/doc/smart/expected.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,13 @@ paths:
content:
application/json:
schema:
type: object
properties:
name:
type: string
avatar_url:
type: string
"$ref": "#/components/schemas/PostUsersRequest"
example:
name: alice
avatar_url: "https://example.com/avatar.png"
foo:
bar:
baz: 42
responses:
'201':
description: returns an user
Expand Down Expand Up @@ -266,3 +264,24 @@ components:
type: string
age:
type: integer
PostUsersRequest:
type: object
properties:
name:
type: string
avatar_url:
type: string
foo:
type: object
properties:
bar:
type: object
properties:
baz:
type: integer
required:
- baz
required:
- bar
required:
- name
11 changes: 1 addition & 10 deletions spec/rails/doc/smart/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,7 @@ paths:
content:
application/json:
schema:
type: object
properties:
name:
type: string
avatar:
"$ref": "#/components/schemas/Avatar"
pets:
type: array
items:
"$ref": "#/components/schemas/Pet"
"$ref": "#/components/schemas/PostUsersRequest"
responses:
'201':
description: returns a user
Expand Down
19 changes: 19 additions & 0 deletions spec/requests/rails_smart_merge_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@

RSpec.describe 'Users', type: :request do
describe '#create' do
it 'accepts missing avatar_url' do
post '/users', headers: { authorization: 'k0kubun', 'Content-Type': 'application/json' }, params: {
name: 'alice',
}.to_json
expect(response.status).to eq(201)
end

it 'accepts nested object' do
post '/users', headers: { authorization: 'k0kubun', 'Content-Type': 'application/json' }, params: {
name: 'alice',
foo: {
bar: {
baz: 42,
},
},
}.to_json
expect(response.status).to eq(201)
end

it 'returns an user' do
post '/users', headers: { authorization: 'k0kubun', 'Content-Type': 'application/json' }, params: {
name: 'alice',
Expand Down
5 changes: 4 additions & 1 deletion spec/roda/doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
"id": {
"type": "integer"
}
}
},
"required": [
"id"
]
},
"example": {
"id": 1
Expand Down