Skip to content

Commit

Permalink
Support reference in arbitrary depth
Browse files Browse the repository at this point in the history
  • Loading branch information
exoego committed Nov 9, 2022
1 parent 2693849 commit 35e3828
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 8 deletions.
17 changes: 9 additions & 8 deletions lib/rspec/openapi/components_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ def update!(base, fresh)
nested_refs = find_non_top_level_nested_refs(base, generated_schema_names)
nested_refs.each do |paths|
# Slice between the parent name and the element before "$ref"
# [..., "Table", "properties", "database", "$ref"]
# ^idx-1 ^idx ^size-idx
# [..., "Table", "properties", "columns", "items", "$ref"]
# ^idx-1 ^idx ^size-idx
idx_properties = paths.size - 1 - paths.reverse.find_index('properties')
needle = paths.slice(idx_properties - 1, paths.size - idx_properties)
# ["components", "schema", "Table", "properties", "database", "$ref"]
# 0 1 2 ^....................^
# ["components", "schema", "Table", "properties", "columns", "items", "$ref"]
# 0 1 2 ^...............................^
# ["components", "schema", "Table", "properties", "owner", "properties", "company", "$ref"]
# 0 1 2 ^...........................................^
needle = paths.slice(2, paths.size - 3)
nested_schema = fresh_schemas.dig(*needle)

# Skip if the property using $ref is not found in the parent schema. The property may be removed.
Expand Down Expand Up @@ -59,8 +60,8 @@ def paths_to_top_level_refs(base)

def find_non_top_level_nested_refs(base, generated_names)
nested_refs = [
*RSpec::OpenAPI::HashHelper.matched_paths(base, 'components.schemas.*.properties.*.$ref'),
*RSpec::OpenAPI::HashHelper.matched_paths(base, 'components.schemas.*.properties.*.*.$ref')
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'properties.*.$ref'),
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'properties.*.items.$ref')
]
# Reject already-generated schemas to reduce unnecessary loop
nested_refs.reject do |paths|
Expand Down
13 changes: 13 additions & 0 deletions lib/rspec/openapi/hash_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,17 @@ def matched_paths(obj, selector)
end
selectors
end

def matched_paths_deeply_nested(obj, begin_selector, end_selector)
path_depth_sizes = paths_to_all_fields(obj).map(&:size).uniq
path_depth_sizes.map do |depth|
diff = depth - begin_selector.count('.') - end_selector.count('.')
if diff >= 0
selector = "#{begin_selector}.#{'*.' * diff}#{end_selector}"
matched_paths(obj, selector)
else
[]
end
end.flatten(1)
end
end
26 changes: 26 additions & 0 deletions spec/rails/app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class UsersController < ApplicationController
def show
render json: find_user(params[:id])
end

private

def find_user(id = nil)
case id
when '1', nil
{
name: 'John Doe',
relations: {
avatar: {
url: 'https://example.com/avatar.jpg',
},
pets: [
{ name: 'doge', age: 8 }
]
}
}
else
raise NotFoundError
end
end
end
1 change: 1 addition & 0 deletions spec/rails/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
defaults format: 'json' do
resources :tables, only: [:index, :show, :create, :update, :destroy]
resources :images, only: [:index, :show]
resources :users, only: [:show]

get '/test_block' => ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['A TEST']] }
end
Expand Down
53 changes: 53 additions & 0 deletions spec/rails/doc/smart/expected.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,33 @@ paths:
storage_size: 12.3
created_at: '2020-07-17T00:00:00+00:00'
updated_at: '2020-07-17T00:00:00+00:00'
"/users/{id}":
get:
responses:
"200":
description: "returns a user"
content:
"application/json":
schema:
"$ref": "#/components/schemas/User"
example:
name: John Doe
relations:
avatar:
url: "https://example.com/avatar.jpg"
pets:
- name: doge
age: 8
summary: show
tags:
- User
parameters:
- name: id
in: path
required: true
schema:
type: integer
example: 1
components:
schemas:
Table:
Expand Down Expand Up @@ -176,3 +203,29 @@ components:
type: string
column_type:
type: string
User:
type: object
properties:
name:
type: string
relations:
type: object
properties:
avatar:
"$ref": "#/components/schemas/Avatar"
pets:
type: array
items:
"$ref": "#/components/schemas/Pet"
Avatar:
type: object
properties:
url:
type: string
Pet:
type: object
properties:
name:
type: string
age:
type: integer
23 changes: 23 additions & 0 deletions spec/rails/doc/smart/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,15 @@ paths:
storage_size: 12.3
created_at: '2020-07-17T00:00:00+00:00'
updated_at: '2020-07-17T00:00:00+00:00'
"/users/{id}":
get:
responses:
'200':
description: returns a user
content:
application/json:
schema:
"$ref": "#/components/schemas/User"
components:
schemas:
Table:
Expand Down Expand Up @@ -247,3 +256,17 @@ components:
properties:
id:
type: integer
User:
type: object
properties:
name:
type: string
relations:
type: object
properties:
avatar:
"$ref": "#/components/schemas/Avatar"
pets:
type: array
items:
"$ref": "#/components/schemas/Pet"
9 changes: 9 additions & 0 deletions spec/requests/rails_smart_merge_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,12 @@
end
end
end

RSpec.describe 'Users', type: :request do
describe '#show' do
it 'returns a user' do
get '/users/1'
expect(response.status).to eq(200)
end
end
end

0 comments on commit 35e3828

Please sign in to comment.