diff --git a/lib/rspec/openapi/components_updater.rb b/lib/rspec/openapi/components_updater.rb index b53b63d7..2db7bba4 100644 --- a/lib/rspec/openapi/components_updater.rb +++ b/lib/rspec/openapi/components_updater.rb @@ -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. @@ -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| diff --git a/lib/rspec/openapi/hash_helper.rb b/lib/rspec/openapi/hash_helper.rb index fb9e915c..5395ec2b 100644 --- a/lib/rspec/openapi/hash_helper.rb +++ b/lib/rspec/openapi/hash_helper.rb @@ -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 diff --git a/spec/rails/app/controllers/users_controller.rb b/spec/rails/app/controllers/users_controller.rb new file mode 100644 index 00000000..07814660 --- /dev/null +++ b/spec/rails/app/controllers/users_controller.rb @@ -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 diff --git a/spec/rails/config/routes.rb b/spec/rails/config/routes.rb index 0ba74542..0daaada7 100644 --- a/spec/rails/config/routes.rb +++ b/spec/rails/config/routes.rb @@ -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 diff --git a/spec/rails/doc/smart/expected.yaml b/spec/rails/doc/smart/expected.yaml index e8b0bf64..cf4f71b9 100644 --- a/spec/rails/doc/smart/expected.yaml +++ b/spec/rails/doc/smart/expected.yaml @@ -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: @@ -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 diff --git a/spec/rails/doc/smart/openapi.yaml b/spec/rails/doc/smart/openapi.yaml index a4ed6de7..5b1d561d 100644 --- a/spec/rails/doc/smart/openapi.yaml +++ b/spec/rails/doc/smart/openapi.yaml @@ -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: @@ -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" diff --git a/spec/requests/rails_smart_merge_spec.rb b/spec/requests/rails_smart_merge_spec.rb index 0cf1d20e..c78b4527 100644 --- a/spec/requests/rails_smart_merge_spec.rb +++ b/spec/requests/rails_smart_merge_spec.rb @@ -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