diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a3ad7f..3af89b7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ['3.0', '3.1', '3.2', '3.3'] + ruby-version: ['3.1', '3.2', '3.3'] steps: - uses: actions/checkout@v4 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7552afe..79756fd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -100,7 +100,7 @@ RSpec/DescribeClass: # Offense count: 4 # Configuration parameters: CountAsOne. RSpec/ExampleLength: - Max: 172 + Max: 186 # Offense count: 24 RSpec/LeakyConstantDeclaration: diff --git a/CHANGELOG.md b/CHANGELOG.md index c178658..cb310d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#75](https://github.com/ruby-grape/grape-swagger-entity/pull/75): Fix handling of entity with class names without "entity" or "entities" - [@numbata](https://github.com/numbata). ### 0.5.5 (2024/09/09) diff --git a/lib/grape-swagger/entity/attribute_parser.rb b/lib/grape-swagger/entity/attribute_parser.rb index 841a0c6..220f35e 100644 --- a/lib/grape-swagger/entity/attribute_parser.rb +++ b/lib/grape-swagger/entity/attribute_parser.rb @@ -5,6 +5,9 @@ module Entity class AttributeParser attr_reader :endpoint + # The list of that doesn't handled by `GrapeSwagger::DocMethods::DataType.primitive?` method + ADDITIONAL_PRIMITIVE_TYPES = %w[string array].freeze + def initialize(endpoint) @endpoint = endpoint end @@ -53,9 +56,15 @@ def direct_model_type?(type) end def ambiguous_model_type?(type) - type&.is_a?(Class) && - !GrapeSwagger::DocMethods::DataType.primitive?(type.name.downcase) && - !type == Array + type&.is_a?(Class) && !primitive_type?(type) + end + + def primitive_type?(type) + type_name = type.name&.downcase + return false if type_name.nil? + + GrapeSwagger::DocMethods::DataType.primitive?(type_name) || + ADDITIONAL_PRIMITIVE_TYPES.include?(type_name) end def data_type_from(entity_options) diff --git a/spec/grape-swagger/entities/response_model_spec.rb b/spec/grape-swagger/entities/response_model_spec.rb index 298457c..33770ee 100644 --- a/spec/grape-swagger/entities/response_model_spec.rb +++ b/spec/grape-swagger/entities/response_model_spec.rb @@ -179,6 +179,16 @@ class Polymorphic < Grape::Entity }, documentation: { desc: 'Polymorphic Number', type: 'Integer' } end + class TagType < CustomType + def tags + %w[Cyan Magenta Yellow Key] + end + end + + class MixedType < Grape::Entity + expose :tags, documentation: { type: TagType, desc: 'Tags', is_array: true } + end + class SomeEntity < Grape::Entity expose :text, documentation: { type: 'string', desc: 'Content of something.' } expose :kind, using: Kind, documentation: { type: 'TheseApi_Kind', desc: 'The kind of this something.' } @@ -190,7 +200,8 @@ class SomeEntity < Grape::Entity expose :values, using: TheseApi::Entities::Values, documentation: { desc: 'Tertiary kind.' } expose :nested, using: TheseApi::Entities::Nested, documentation: { desc: 'Nested object.' } expose :nested_child, using: TheseApi::Entities::NestedChild, documentation: { desc: 'Nested child object.' } - expose :polymorphic, using: TheseApi::Entities::Polymorphic, documentation: { desc: 'Polymorphic Model' } + expose :polymorphic, using: TheseApi::Entities::Polymorphic, documentation: { desc: 'A polymorphic model.' } + expose :mixed, using: TheseApi::Entities::MixedType, documentation: { desc: 'A model with mix of types.' } expose :merged_attribute, using: ThisApi::Entities::Nested, merge: true end end @@ -365,6 +376,17 @@ def app } ) + expect(subject['TheseApi_Entities_MixedType']).to eql( + 'properties' => { + 'tags' => { + 'description' => 'Tags', + 'items' => { '$ref' => '#/definitions/TheseApi_Entities_TagType' }, + 'type' => 'array' + } + }, + 'type' => 'object' + ) + expect(subject['TheseApi_Entities_SomeEntity']).to eql( 'type' => 'object', 'properties' => { @@ -382,7 +404,11 @@ def app 'code' => { 'type' => 'string', 'description' => 'Error code' }, 'message' => { 'type' => 'string', 'description' => 'Error message' }, 'polymorphic' => { '$ref' => '#/definitions/TheseApi_Entities_Polymorphic', - 'description' => 'Polymorphic Model' }, + 'description' => 'A polymorphic model.' }, + 'mixed' => { + '$ref' => '#/definitions/TheseApi_Entities_MixedType', + 'description' => 'A model with mix of types.' + }, 'attr' => { 'type' => 'string', 'description' => 'Attribute' } }, 'required' => %w[attr], diff --git a/spec/grape-swagger/entity/attribute_parser_spec.rb b/spec/grape-swagger/entity/attribute_parser_spec.rb index 99f9d71..c5686fe 100644 --- a/spec/grape-swagger/entity/attribute_parser_spec.rb +++ b/spec/grape-swagger/entity/attribute_parser_spec.rb @@ -35,6 +35,24 @@ it { is_expected.to include(example: %w[green blue].map { { name: _1 } }) } end + context 'when the entity is implicit Entity' do + let(:entity_type) do + Class.new(ThisApi::Entities::Tag) do + def self.name + 'ThisApi::Tag' + end + + def self.to_s + name + end + end + end + let(:entity_options) { { documentation: { type: entity_type, is_array: true, min_items: 1 } } } + + it { is_expected.to include('type' => 'array') } + it { is_expected.to include('items' => { '$ref' => '#/definitions/Tag' }) } + end + context 'when it contains min_items' do let(:entity_options) { { using: ThisApi::Entities::Tag, documentation: { is_array: true, min_items: 1 } } } diff --git a/spec/support/shared_contexts/custom_type_parser.rb b/spec/support/shared_contexts/custom_type_parser.rb new file mode 100644 index 0000000..7095a07 --- /dev/null +++ b/spec/support/shared_contexts/custom_type_parser.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +CustomType = Class.new + +class CustomTypeParser + attr_reader :model, :endpoint + + def initialize(model, endpoint) + @model = model + @endpoint = endpoint + end + + def call + { + model.name.to_sym => { + type: 'custom_type', + description: "it's a custom type", + data: { + name: model.name + } + } + } + end +end + +GrapeSwagger.model_parsers.register(CustomTypeParser, CustomType)