diff --git a/.gitignore b/.gitignore index b4358f5..d88fe50 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ tmp api/ .vscode spec/fixtures/pmm.json -spec/fixtures/arena.json +spec/fixtures/nested.json diff --git a/lib/starter/importer/nested_params.rb b/lib/starter/importer/nested_params.rb index 17ec5dc..c5eeb7f 100644 --- a/lib/starter/importer/nested_params.rb +++ b/lib/starter/importer/nested_params.rb @@ -4,6 +4,8 @@ module Starter module Importer class NestedParams < Parameter def initialize(name:, definition:) + @kind = :body + @nested = [] @name = name @definition = definition end diff --git a/lib/starter/importer/parameter.rb b/lib/starter/importer/parameter.rb index ad2bc95..1f0267f 100644 --- a/lib/starter/importer/parameter.rb +++ b/lib/starter/importer/parameter.rb @@ -1,4 +1,4 @@ -# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity +# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ClassLength # frozen_string_literal: false module Starter @@ -24,8 +24,6 @@ def nested? @nested.present? end - private - # initialize helper # def validate_parameters(definition:, components:) @@ -51,7 +49,7 @@ def prepare_attributes(definition:, components:) # rubocop:disable Metrics/Metho end when :body definition['in'] = 'body' - schema = definition['content'].values.first['schema'] + schema = definition['content'] ? definition['content'].values.first['schema'] : definition if schema.key?('$ref') path = schema['$ref'].split('/')[2..] @@ -64,7 +62,7 @@ def prepare_attributes(definition:, components:) # rubocop:disable Metrics/Metho end end - def handle_body(definition:, properties:) + def handle_body(definition:, properties:) # rubocop:disable Metrics/MethodLength if simple_object?(properties:) name = properties['properties'].keys.first type = properties.dig('properties', name, 'type') || 'array' @@ -79,27 +77,37 @@ def handle_body(definition:, properties:) definition['type'] = properties['type'].presence || 'JSON' return [nil, definition] if properties.nil? || properties['properties'].nil? - properties['properties'].each do |nested_name, definition| - definition['required'] = properties['required']&.include?(nested_name) || false - @nested << NestedParams.new(name: nested_name, definition:) + properties['properties'].each do |nested_name, nested_definition| + nested_definition['required'] = + properties['required'] ? properties['required'].include?(nested_name) : false + nested = NestedParams.new(name: nested_name, definition: nested_definition) + nested.prepare_attributes(definition: nested.definition, components: {}) + nested.name = nested_name + @nested << nested end - [nil, definition] + + [self.name, definition] else # others - [nil, definition.merge(properties)] + [nil, properties ? definition.merge(properties) : definition] end end # handle_body helper, check/find/define types # def object?(definition:) - definition['content'].keys.first.include?('application/json') + definition['type'] == 'object' || + definition['content']&.keys&.first&.include?('application/json') end def simple_object?(properties:) - properties.key?('properties') && + list_of_object?(properties:) && properties['properties'].length == 1 end + def list_of_object?(properties:) + properties&.key?('properties') + end + # to_s helper # def serialized_object @@ -156,4 +164,4 @@ def documentation end end -# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity +# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ClassLength diff --git a/spec/lib/importer/parameter_spec.rb b/spec/lib/importer/parameter_spec.rb index d90ef1b..df7eb82 100644 --- a/spec/lib/importer/parameter_spec.rb +++ b/spec/lib/importer/parameter_spec.rb @@ -291,46 +291,141 @@ "requires :postApiV1CalibrationsCreating, type: JSON, documentation: { in: 'body' } do" ) expect(subject.to_s).to include( - "requires :crop, type: String, documentation: { desc: 'The Crop of it.' }" + "requires :crop, type: String, documentation: { desc: 'The Crop of it.', in: 'body' }" ) expect(subject.to_s).to include( - "requires :name, type: String, documentation: { desc: 'The Name of it.' }" + "requires :name, type: String, documentation: { desc: 'The Name of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :projects, type: Array[String], documentation: { desc: 'The project(s) of it.' }" + "optional :projects, type: Array[String], documentation: { desc: 'The project(s) of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :pools, type: Array[String], documentation: { desc: 'The pool(s) of it.' }" + "optional :pools, type: Array[String], documentation: { desc: 'The pool(s) of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :material_level, type: Array[String], documentation: { desc: 'The material level(s) of it.' }" + "optional :material_level, type: Array[String], documentation: { desc: 'The material level(s) of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :material_groups, type: Array[String], documentation: { desc: 'The material group(s) of it.' }" + "optional :material_groups, type: Array[String], documentation: { desc: 'The material group(s) of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :path, type: String, documentation: { desc: 'The path of it.' }" + "optional :path, type: String, documentation: { desc: 'The path of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :testers, type: Array[String], documentation: { desc: 'The tester(s) of it.' }" + "optional :testers, type: Array[String], documentation: { desc: 'The tester(s) of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :locations, type: Array[String], documentation: { desc: 'The location(s) of it.' }" + "optional :locations, type: Array[String], documentation: { desc: 'The location(s) of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :years, type: Array[Integer], documentation: { desc: 'The year(s) of it.' }" + "optional :years, type: Array[Integer], documentation: { desc: 'The year(s) of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :tags, type: Array[String], documentation: { desc: 'The tag(s) of it.' }" + "optional :tags, type: Array[String], documentation: { desc: 'The tag(s) of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :treatments, type: Array[String], documentation: { desc: 'The treatment(s) of it.' }" + "optional :treatments, type: Array[String], documentation: { desc: 'The treatment(s) of it.', in: 'body' }" ) expect(subject.to_s).to include( - "optional :id, type: Integer, documentation: { desc: 'The ID of it (possible on back step, so editing is possible).', format: 'int32' }" + "optional :id, type: Integer, documentation: { desc: 'The ID of it (possible on back step, so editing is possible).', in: 'body', format: 'int32' }" + ) + end + end + + describe 'request body: nested object' do + let(:definition) do + { + 'content' => { + 'application/json' => { + 'schema' => { + '$ref' => '#/components/schemas/ordered' + } + } + }, + 'required' => true + } + end + + let(:components) do + { + 'schemas' => { + 'ordered' => { + 'type' => 'object', + 'properties' => { + 'order' => { + 'type' => 'object', + 'properties' => { + 'facet' => { + 'type' => 'string', + 'default' => 'score', + 'enum' => %w[ + created_at + updated_at + random + ] + }, + 'dir' => { + 'type' => 'string', + 'default' => 'asc', + 'enum' => %w[ + asc + desc + ] + } + }, + 'description' => 'Specify result order' + }, + 'per_page' => { + 'type' => 'integer', + 'format' => 'int32', + 'default' => 24 + }, + 'page' => { + 'type' => 'integer', + 'format' => 'int32', + 'default' => 1 + }, + 'choose' => { + 'type' => 'array', + 'items' => { + 'type' => 'string', + 'enum' => %w[a b] + } + } + }, + 'description' => 'something nested body request' + } + } + } + end + + specify do + expect(subject.name).to eql 'ordered' + expect(subject.definition.keys).to match_array %w[ + required content in type + ] + expect(subject.definition['type']).to eql 'object' + + expect(subject.to_s).to include( + "requires :ordered, type: JSON, documentation: { in: 'body' } do" + ) + expect(subject.to_s).to include( + "optional :order, type: JSON, documentation: { desc: 'Specify result order', in: 'body' } do" + ) + expect(subject.to_s).to include( + "optional :facet, type: String, documentation: { in: 'body' }" + ) + expect(subject.to_s).to include( + "optional :dir, type: String, documentation: { in: 'body' }" + ) + expect(subject.to_s).to include( + "optional :per_page, type: Integer, documentation: { in: 'body', format: 'int32' }" + ) + expect(subject.to_s).to include( + "optional :page, type: Integer, documentation: { in: 'body', format: 'int32' }" ) expect(subject.to_s).to include( - 'end' + "optional :choose, type: Array[String], documentation: { in: 'body' }" ) end end