diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7a6c202..ecc8746 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2024-09-09 22:35:29 UTC using RuboCop version 1.66.1. +# on 2025-05-08 20:42:22 UTC using RuboCop version 1.75.5. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -44,7 +44,7 @@ Metrics/AbcSize: # Offense count: 2 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 112 + Max: 117 # Offense count: 2 # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -64,7 +64,7 @@ Metrics/PerceivedComplexity: # Offense count: 5 # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, normalcase, non_integer -# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 +# AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 Naming/VariableNumber: Exclude: - 'spec/grape-swagger/entities/response_model_spec.rb' @@ -100,9 +100,9 @@ RSpec/DescribeClass: # Offense count: 4 # Configuration parameters: CountAsOne. RSpec/ExampleLength: - Max: 187 + Max: 211 -# Offense count: 24 +# Offense count: 26 RSpec/LeakyConstantDeclaration: Exclude: - 'spec/grape-swagger/entities/response_model_spec.rb' @@ -118,14 +118,14 @@ RSpec/MultipleDescribes: RSpec/MultipleExpectations: Max: 11 -# Offense count: 20 +# Offense count: 21 # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. # SupportedStyles: always, named_only RSpec/NamedSubject: Exclude: - 'spec/grape-swagger/entities/response_model_spec.rb' -# Offense count: 39 +# Offense count: 40 # Configuration parameters: AllowedGroups. RSpec/NestedGroups: Max: 5 diff --git a/lib/grape-swagger/entity/parser.rb b/lib/grape-swagger/entity/parser.rb index c93b426..437f633 100644 --- a/lib/grape-swagger/entity/parser.rb +++ b/lib/grape-swagger/entity/parser.rb @@ -135,9 +135,15 @@ def parse_nested(entity_name, entity_options, parent_model = nil) end def required_params(params) - params.select { |_, options| options.fetch(:documentation, {}).fetch(:required, false) } - .map { |(key, options)| [options.fetch(:as, key), options] } - .map(&:first) + params.each_with_object(Set.new) do |(key, options), accum| + required = if options.fetch(:documentation, {}).key?(:required) + options.dig(:documentation, :required) + else + !options.key?(:if) && !options.key?(:unless) + end + + accum.add(options.fetch(:as, key)) if required + end.to_a end def with_required(hash, required) diff --git a/spec/grape-swagger/entities/response_model_spec.rb b/spec/grape-swagger/entities/response_model_spec.rb index 018f804..fb57f5d 100644 --- a/spec/grape-swagger/entities/response_model_spec.rb +++ b/spec/grape-swagger/entities/response_model_spec.rb @@ -44,7 +44,8 @@ def app 'properties' => { 'code' => { 'type' => 'string', 'description' => 'Error code' }, 'message' => { 'type' => 'string', 'description' => 'Error message' } - } + }, + 'required' => %w[code message] ) expect(subject['definitions'].keys).to include 'ThisApi_Entities_Something' @@ -66,24 +67,32 @@ def app 'code' => { 'type' => 'string', 'description' => 'Error code' }, 'message' => { 'type' => 'string', 'description' => 'Error message' }, 'attr' => { 'type' => 'string', 'description' => 'Attribute' } }, - 'required' => ['attr'] + 'required' => %w[text colors hidden_attr created_at kind kind2 kind3 tags relation + attr code message] ) expect(subject['definitions'].keys).to include 'ThisApi_Entities_Kind' expect(subject['definitions']['ThisApi_Entities_Kind']).to eq( - 'type' => 'object', 'properties' => { 'title' => { 'type' => 'string', 'description' => 'Title of the kind.' }, - 'content' => { 'description' => 'Content', 'type' => 'string', - 'x-some' => 'stuff' } } + 'type' => 'object', + 'properties' => { + 'title' => { 'type' => 'string', 'description' => 'Title of the kind.' }, + 'content' => { 'type' => 'string', 'description' => 'Content', 'x-some' => 'stuff' } + }, + 'required' => %w[title content] ) expect(subject['definitions'].keys).to include 'ThisApi_Entities_Relation' expect(subject['definitions']['ThisApi_Entities_Relation']).to eq( - 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string', 'description' => 'Name' } } + 'type' => 'object', + 'properties' => { 'name' => { 'type' => 'string', 'description' => 'Name' } }, + 'required' => %w[name] ) expect(subject['definitions'].keys).to include 'ThisApi_Entities_Tag' expect(subject['definitions']['ThisApi_Entities_Tag']).to eq( - 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string', 'description' => 'Name' } } + 'type' => 'object', + 'properties' => { 'name' => { 'type' => 'string', 'description' => 'Name' } }, + 'required' => %w[name] ) end end @@ -134,8 +143,10 @@ class Nested < Grape::Entity end expose :nested_required do expose :some1, documentation: { required: true, desc: 'Required some 1' } - expose :attr, as: :some2, documentation: { required: true, desc: 'Required some 2' } - expose :some3, documentation: { desc: 'Optional some 3' } + expose :attr, as: :some2, documentation: { desc: 'Required some 2' } + expose :some3, documentation: { required: false, desc: 'Optional some 3' } + expose :some4, if: -> { true }, documentation: { desc: 'Optional some 4' } + expose :some5, unless: -> { true }, documentation: { desc: 'Optional some 5' } end expose :nested_array, documentation: { type: 'Array', desc: 'Nested array' } do @@ -235,7 +246,8 @@ def app 'uuid' => { 'type' => 'string', 'format' => 'own', 'description' => 'customer uuid', 'example' => 'e3008fba-d53d-4bcc-a6ae-adc56dff8020' }, 'color' => { 'type' => 'string', 'enum' => %w[red blue], 'description' => 'Color' } - } + }, + 'required' => %w[guid uuid color] ) expect(subject['TheseApi_Entities_Kind']).to eql( 'type' => 'object', @@ -244,16 +256,21 @@ def app 'readOnly' => true }, 'title' => { 'type' => 'string', 'description' => 'Title of the kind.', 'readOnly' => false }, 'type' => { 'type' => 'string', 'description' => 'Type of the kind.', 'readOnly' => true } - } + }, + 'required' => %w[id title type] ) expect(subject['TheseApi_Entities_Tag']).to eql( - 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string', 'description' => 'Name', - 'example' => 'random_tag' } } + 'type' => 'object', + 'properties' => { 'name' => { 'type' => 'string', 'description' => 'Name', 'example' => 'random_tag' } }, + 'required' => %w[name] ) expect(subject['TheseApi_Entities_Relation']).to eql( - 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string', 'description' => 'Name' } } + 'type' => 'object', + 'properties' => { 'name' => { 'type' => 'string', 'description' => 'Name' } }, + 'required' => %w[name] ) expect(subject['TheseApi_Entities_Nested']).to eq( + 'type' => 'object', 'properties' => { 'nested' => { 'type' => 'object', @@ -261,13 +278,15 @@ def app 'some1' => { 'type' => 'string', 'description' => 'Nested some 1' }, 'some2' => { 'type' => 'string', 'description' => 'Nested some 2' } }, - 'description' => 'Nested entity' + 'description' => 'Nested entity', + 'required' => %w[some1 some2] }, 'aliased' => { 'type' => 'object', 'properties' => { 'some1' => { 'type' => 'string', 'description' => 'Alias some 1' } - } + }, + 'required' => %w[some1] }, 'deep_nested' => { 'type' => 'object', @@ -277,17 +296,21 @@ def app 'properties' => { 'level_2' => { 'type' => 'string', 'description' => 'Level 2' } }, - 'description' => 'More deepest nested entity' + 'description' => 'More deepest nested entity', + 'required' => %w[level_2] } }, - 'description' => 'Deep nested entity' + 'description' => 'Deep nested entity', + 'required' => %w[level_1] }, 'nested_required' => { 'type' => 'object', 'properties' => { 'some1' => { 'type' => 'string', 'description' => 'Required some 1' }, 'some2' => { 'type' => 'string', 'description' => 'Required some 2' }, - 'some3' => { 'type' => 'string', 'description' => 'Optional some 3' } + 'some3' => { 'type' => 'string', 'description' => 'Optional some 3' }, + 'some4' => { 'type' => 'string', 'description' => 'Optional some 4' }, + 'some5' => { 'type' => 'string', 'description' => 'Optional some 5' } }, 'required' => %w[some1 some2] }, @@ -298,14 +321,16 @@ def app 'properties' => { 'id' => { 'type' => 'integer', 'format' => 'int32', 'description' => 'Collection element id' }, 'name' => { 'type' => 'string', 'description' => 'Collection element name' } - } + }, + 'required' => %w[id name] }, 'description' => 'Nested array' } }, - 'type' => 'object' + 'required' => %w[nested aliased deep_nested nested_required nested_array] ) expect(subject['TheseApi_Entities_NestedChild']).to eq( + 'type' => 'object', 'properties' => { 'nested' => { 'type' => 'object', @@ -314,14 +339,16 @@ def app 'some2' => { 'type' => 'string', 'description' => 'Nested some 2' }, 'some3' => { 'type' => 'string', 'description' => 'Nested some 3' } }, - 'description' => 'Nested entity' + 'description' => 'Nested entity', + 'required' => %w[some1 some2 some3] }, 'aliased' => { 'type' => 'object', 'properties' => { 'some1' => { 'type' => 'string', 'description' => 'Alias some 1' }, 'some2' => { 'type' => 'string', 'description' => 'Alias some 2' } - } + }, + 'required' => %w[some1 some2] }, 'deep_nested' => { 'type' => 'object', @@ -337,20 +364,25 @@ def app 'description' => 'Level 3' } }, - 'description' => 'Level 2' + 'description' => 'Level 2', + 'required' => %w[level_3] } }, - 'description' => 'More deepest nested entity' + 'description' => 'More deepest nested entity', + 'required' => %w[level_2] } }, - 'description' => 'Deep nested entity' + 'description' => 'Deep nested entity', + 'required' => %w[level_1] }, 'nested_required' => { 'type' => 'object', 'properties' => { 'some1' => { 'type' => 'string', 'description' => 'Required some 1' }, 'some2' => { 'type' => 'string', 'description' => 'Required some 2' }, - 'some3' => { 'type' => 'string', 'description' => 'Optional some 3' } + 'some3' => { 'type' => 'string', 'description' => 'Optional some 3' }, + 'some4' => { 'type' => 'string', 'description' => 'Optional some 4' }, + 'some5' => { 'type' => 'string', 'description' => 'Optional some 5' } }, 'required' => %w[some1 some2] }, @@ -362,12 +394,13 @@ def app 'id' => { 'type' => 'integer', 'format' => 'int32', 'description' => 'Collection element id' }, 'name' => { 'type' => 'string', 'description' => 'Collection element name' }, 'category' => { 'type' => 'string', 'description' => 'Collection element category' } - } + }, + 'required' => %w[id name category] }, 'description' => 'Nested array' } }, - 'type' => 'object' + 'required' => %w[nested aliased deep_nested nested_required nested_array] ) expect(subject['TheseApi_Entities_Polymorphic']).to eql( 'type' => 'object', @@ -380,6 +413,7 @@ def app ) expect(subject['TheseApi_Entities_MixedType']).to eql( + 'type' => 'object', 'properties' => { 'tags' => { 'description' => 'Tags', @@ -387,7 +421,7 @@ def app 'type' => 'array' } }, - 'type' => 'object' + 'required' => %w[tags] ) expect(subject['TheseApi_Entities_SomeEntity']).to eql( @@ -414,7 +448,8 @@ def app }, 'attr' => { 'type' => 'string', 'description' => 'Attribute' } }, - 'required' => %w[attr], + 'required' => %w[text kind kind2 kind3 tags relation values nested nested_child + polymorphic mixed attr code message], 'description' => 'TheseApi_Entities_SomeEntity model' ) end