grape 1.6.2 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -1
  3. data/CONTRIBUTING.md +30 -0
  4. data/README.md +146 -23
  5. data/UPGRADING.md +15 -0
  6. data/grape.gemspec +2 -2
  7. data/lib/grape/api/instance.rb +1 -1
  8. data/lib/grape/dry_types.rb +12 -0
  9. data/lib/grape/dsl/api.rb +0 -2
  10. data/lib/grape/dsl/callbacks.rb +0 -2
  11. data/lib/grape/dsl/configuration.rb +0 -2
  12. data/lib/grape/dsl/desc.rb +2 -16
  13. data/lib/grape/dsl/helpers.rb +0 -2
  14. data/lib/grape/dsl/inside_route.rb +34 -30
  15. data/lib/grape/dsl/middleware.rb +0 -2
  16. data/lib/grape/dsl/parameters.rb +10 -7
  17. data/lib/grape/dsl/request_response.rb +1 -3
  18. data/lib/grape/dsl/routing.rb +4 -2
  19. data/lib/grape/dsl/settings.rb +0 -2
  20. data/lib/grape/dsl/validations.rb +0 -15
  21. data/lib/grape/endpoint.rb +2 -2
  22. data/lib/grape/error_formatter/json.rb +7 -1
  23. data/lib/grape/exceptions/base.rb +3 -2
  24. data/lib/grape/exceptions/missing_group_type.rb +8 -1
  25. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  26. data/lib/grape/exceptions/unsupported_group_type.rb +8 -1
  27. data/lib/grape/exceptions/validation.rb +0 -4
  28. data/lib/grape/locale/en.yml +9 -8
  29. data/lib/grape/middleware/auth/dsl.rb +0 -1
  30. data/lib/grape/middleware/error.rb +2 -2
  31. data/lib/grape/middleware/stack.rb +1 -1
  32. data/lib/grape/request.rb +3 -1
  33. data/lib/grape/router/attribute_translator.rb +1 -1
  34. data/lib/grape/types/invalid_value.rb +8 -0
  35. data/lib/grape/util/cache.rb +1 -1
  36. data/lib/grape/util/json.rb +2 -0
  37. data/lib/grape/validations/attributes_doc.rb +58 -0
  38. data/lib/grape/validations/params_scope.rb +67 -41
  39. data/lib/grape/validations/types/array_coercer.rb +0 -2
  40. data/lib/grape/validations/types/dry_type_coercer.rb +3 -7
  41. data/lib/grape/validations/types/invalid_value.rb +0 -7
  42. data/lib/grape/validations/types/primitive_coercer.rb +14 -6
  43. data/lib/grape/validations/types/set_coercer.rb +0 -2
  44. data/lib/grape/validations/types.rb +98 -30
  45. data/lib/grape/validations/validators/{all_or_none.rb → all_or_none_of_validator.rb} +0 -2
  46. data/lib/grape/validations/validators/{at_least_one_of.rb → at_least_one_of_validator.rb} +0 -2
  47. data/lib/grape/validations/validators/base.rb +7 -0
  48. data/lib/grape/validations/validators/{exactly_one_of.rb → exactly_one_of_validator.rb} +0 -2
  49. data/lib/grape/validations/validators/{mutual_exclusion.rb → mutual_exclusion_validator.rb} +0 -2
  50. data/lib/grape/validations.rb +16 -12
  51. data/lib/grape/version.rb +1 -1
  52. data/lib/grape.rb +74 -28
  53. data/spec/grape/api/custom_validations_spec.rb +41 -2
  54. data/spec/grape/api/deeply_included_options_spec.rb +0 -2
  55. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -2
  56. data/spec/grape/api/documentation_spec.rb +59 -0
  57. data/spec/grape/api/inherited_helpers_spec.rb +0 -2
  58. data/spec/grape/api/instance_spec.rb +0 -1
  59. data/spec/grape/api/invalid_format_spec.rb +0 -2
  60. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -2
  61. data/spec/grape/api/nested_helpers_spec.rb +0 -2
  62. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -2
  63. data/spec/grape/api/parameters_modification_spec.rb +0 -2
  64. data/spec/grape/api/patch_method_helpers_spec.rb +0 -2
  65. data/spec/grape/api/recognize_path_spec.rb +0 -2
  66. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -2
  67. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -2
  68. data/spec/grape/api/routes_with_requirements_spec.rb +0 -2
  69. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -2
  70. data/spec/grape/api/shared_helpers_spec.rb +0 -2
  71. data/spec/grape/api_remount_spec.rb +0 -1
  72. data/spec/grape/api_spec.rb +23 -25
  73. data/spec/grape/config_spec.rb +0 -2
  74. data/spec/grape/dsl/callbacks_spec.rb +0 -2
  75. data/spec/grape/dsl/desc_spec.rb +2 -2
  76. data/spec/grape/dsl/headers_spec.rb +2 -4
  77. data/spec/grape/dsl/helpers_spec.rb +0 -2
  78. data/spec/grape/dsl/inside_route_spec.rb +10 -12
  79. data/spec/grape/dsl/logger_spec.rb +0 -2
  80. data/spec/grape/dsl/middleware_spec.rb +0 -2
  81. data/spec/grape/dsl/parameters_spec.rb +0 -2
  82. data/spec/grape/dsl/request_response_spec.rb +6 -8
  83. data/spec/grape/dsl/routing_spec.rb +1 -3
  84. data/spec/grape/dsl/settings_spec.rb +0 -2
  85. data/spec/grape/dsl/validations_spec.rb +0 -17
  86. data/spec/grape/endpoint/declared_spec.rb +2 -4
  87. data/spec/grape/endpoint_spec.rb +29 -9
  88. data/spec/grape/entity_spec.rb +0 -1
  89. data/spec/grape/exceptions/base_spec.rb +16 -2
  90. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -2
  91. data/spec/grape/exceptions/invalid_accept_header_spec.rb +3 -2
  92. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -2
  93. data/spec/grape/exceptions/invalid_response_spec.rb +0 -2
  94. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +1 -3
  95. data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
  96. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -2
  97. data/spec/grape/exceptions/missing_option_spec.rb +1 -3
  98. data/spec/grape/exceptions/unknown_options_spec.rb +0 -2
  99. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -2
  100. data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
  101. data/spec/grape/exceptions/validation_errors_spec.rb +0 -1
  102. data/spec/grape/exceptions/validation_spec.rb +1 -3
  103. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -2
  104. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -2
  105. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -2
  106. data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
  107. data/spec/grape/integration/rack_sendfile_spec.rb +0 -2
  108. data/spec/grape/integration/rack_spec.rb +6 -7
  109. data/spec/grape/loading_spec.rb +0 -2
  110. data/spec/grape/middleware/auth/base_spec.rb +0 -1
  111. data/spec/grape/middleware/auth/dsl_spec.rb +0 -2
  112. data/spec/grape/middleware/auth/strategies_spec.rb +0 -2
  113. data/spec/grape/middleware/base_spec.rb +7 -7
  114. data/spec/grape/middleware/error_spec.rb +6 -1
  115. data/spec/grape/middleware/exception_spec.rb +0 -2
  116. data/spec/grape/middleware/formatter_spec.rb +6 -8
  117. data/spec/grape/middleware/globals_spec.rb +0 -2
  118. data/spec/grape/middleware/stack_spec.rb +0 -2
  119. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -2
  120. data/spec/grape/middleware/versioner/header_spec.rb +18 -4
  121. data/spec/grape/middleware/versioner/param_spec.rb +0 -2
  122. data/spec/grape/middleware/versioner/path_spec.rb +0 -2
  123. data/spec/grape/middleware/versioner_spec.rb +0 -2
  124. data/spec/grape/named_api_spec.rb +0 -2
  125. data/spec/grape/parser_spec.rb +0 -2
  126. data/spec/grape/path_spec.rb +0 -2
  127. data/spec/grape/presenters/presenter_spec.rb +0 -2
  128. data/spec/grape/request_spec.rb +0 -2
  129. data/spec/grape/util/inheritable_setting_spec.rb +0 -1
  130. data/spec/grape/util/inheritable_values_spec.rb +0 -1
  131. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -1
  132. data/spec/grape/util/stackable_values_spec.rb +0 -1
  133. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -1
  134. data/spec/grape/validations/attributes_doc_spec.rb +153 -0
  135. data/spec/grape/validations/instance_behaivour_spec.rb +0 -2
  136. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -2
  137. data/spec/grape/validations/params_scope_spec.rb +315 -86
  138. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -2
  139. data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
  140. data/spec/grape/validations/types/primitive_coercer_spec.rb +20 -5
  141. data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
  142. data/spec/grape/validations/types_spec.rb +28 -2
  143. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -2
  144. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -2
  145. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -2
  146. data/spec/grape/validations/validators/coerce_spec.rb +0 -2
  147. data/spec/grape/validations/validators/default_spec.rb +0 -2
  148. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -2
  149. data/spec/grape/validations/validators/except_values_spec.rb +0 -2
  150. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -2
  151. data/spec/grape/validations/validators/presence_spec.rb +0 -2
  152. data/spec/grape/validations/validators/regexp_spec.rb +0 -2
  153. data/spec/grape/validations/validators/same_as_spec.rb +0 -2
  154. data/spec/grape/validations/validators/values_spec.rb +19 -2
  155. data/spec/grape/validations_spec.rb +78 -27
  156. data/spec/integration/multi_json/json_spec.rb +0 -2
  157. data/spec/integration/multi_xml/xml_spec.rb +0 -2
  158. data/spec/spec_helper.rb +9 -4
  159. metadata +134 -122
  160. data/spec/grape/dsl/configuration_spec.rb +0 -16
  161. data/spec/grape/validations/attributes_iterator_spec.rb +0 -6
  162. data/spec/support/eager_load.rb +0 -19
  163. /data/lib/grape/validations/validators/{allow_blank.rb → allow_blank_validator.rb} +0 -0
  164. /data/lib/grape/validations/validators/{as.rb → as_validator.rb} +0 -0
  165. /data/lib/grape/validations/validators/{coerce.rb → coerce_validator.rb} +0 -0
  166. /data/lib/grape/validations/validators/{default.rb → default_validator.rb} +0 -0
  167. /data/lib/grape/validations/validators/{except_values.rb → except_values_validator.rb} +0 -0
  168. /data/lib/grape/validations/validators/{presence.rb → presence_validator.rb} +0 -0
  169. /data/lib/grape/validations/validators/{regexp.rb → regexp_validator.rb} +0 -0
  170. /data/lib/grape/validations/validators/{same_as.rb → same_as_validator.rb} +0 -0
  171. /data/lib/grape/validations/validators/{values.rb → values_validator.rb} +0 -0
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/concern'
4
-
5
3
  module Grape
6
4
  module DSL
7
5
  # Defines DSL methods, meant to be applied to a ParamsScope, which define
@@ -64,7 +62,12 @@ module Grape
64
62
  params_block = named_params.fetch(name) do
65
63
  raise "Params :#{name} not found!"
66
64
  end
67
- instance_exec(options, &params_block)
65
+
66
+ if options.empty?
67
+ instance_exec(options, &params_block)
68
+ else
69
+ instance_exec(**options, &params_block)
70
+ end
68
71
  end
69
72
  end
70
73
  alias use_scope use
@@ -150,8 +153,8 @@ module Grape
150
153
 
151
154
  # check type for optional parameter group
152
155
  if attrs && block
153
- raise Grape::Exceptions::MissingGroupTypeError.new if type.nil?
154
- raise Grape::Exceptions::UnsupportedGroupTypeError.new unless Grape::Validations::Types.group?(type)
156
+ raise Grape::Exceptions::MissingGroupType if type.nil?
157
+ raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
155
158
  end
156
159
 
157
160
  if opts[:using]
@@ -219,8 +222,8 @@ module Grape
219
222
  else
220
223
  # @declared_params also includes hashes of options and such, but those
221
224
  # won't be flattened out.
222
- @declared_params.flatten.any? do |declared_param|
223
- first_hash_key_or_param(declared_param) == param
225
+ @declared_params.flatten.any? do |declared_param_attr|
226
+ first_hash_key_or_param(declared_param_attr.key) == param
224
227
  end
225
228
  end
226
229
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/concern'
4
-
5
3
  module Grape
6
4
  module DSL
7
5
  module RequestResponse
@@ -127,7 +125,7 @@ module Grape
127
125
  :base_only_rescue_handlers
128
126
  end
129
127
 
130
- namespace_reverse_stackable handler_type, args.map { |arg| [arg, handler] }.to_h
128
+ namespace_reverse_stackable(handler_type, args.to_h { |arg| [arg, handler] })
131
129
  end
132
130
 
133
131
  namespace_stackable(:rescue_options, options)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/concern'
4
-
5
3
  module Grape
6
4
  module DSL
7
5
  module Routing
@@ -79,6 +77,10 @@ module Grape
79
77
  namespace_inheritable(:do_not_route_options, true)
80
78
  end
81
79
 
80
+ def do_not_document!
81
+ namespace_inheritable(:do_not_document, true)
82
+ end
83
+
82
84
  def mount(mounts, *opts)
83
85
  mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
84
86
  mounts.each_pair do |app, path|
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/concern'
4
-
5
3
  module Grape
6
4
  module DSL
7
5
  # Keeps track of settings (implemented as key-value pairs, grouped by
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/concern'
4
-
5
3
  module Grape
6
4
  module DSL
7
5
  module Validations
@@ -32,7 +30,6 @@ module Grape
32
30
  unset_namespace_stackable :declared_params
33
31
  unset_namespace_stackable :validations
34
32
  unset_namespace_stackable :params
35
- unset_description_field :params
36
33
  end
37
34
 
38
35
  # Opens a root-level ParamsScope, defining parameter coercions and
@@ -41,18 +38,6 @@ module Grape
41
38
  def params(&block)
42
39
  Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
43
40
  end
44
-
45
- def document_attribute(names, opts)
46
- setting = description_field(:params)
47
- setting ||= description_field(:params, {})
48
- Array(names).each do |name|
49
- full_name = name[:full_name].to_s
50
- setting[full_name] ||= {}
51
- setting[full_name].merge!(opts)
52
-
53
- namespace_stackable(:params, full_name => opts)
54
- end
55
- end
56
41
  end
57
42
  end
58
43
  end
@@ -299,7 +299,7 @@ module Grape
299
299
 
300
300
  if namespace_inheritable(:version)
301
301
  stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
302
- versions: namespace_inheritable(:version) ? namespace_inheritable(:version).flatten : nil,
302
+ versions: namespace_inheritable(:version)&.flatten,
303
303
  version_options: namespace_inheritable(:version_options),
304
304
  prefix: namespace_inheritable(:root_prefix),
305
305
  mount_path: namespace_stackable(:mount_path).first
@@ -325,7 +325,7 @@ module Grape
325
325
  private :build_stack, :build_helpers
326
326
 
327
327
  def execute
328
- @block ? @block.call(self) : nil
328
+ @block&.call(self)
329
329
  end
330
330
 
331
331
  def helpers
@@ -21,9 +21,15 @@ module Grape
21
21
  if message.is_a?(Exceptions::ValidationErrors) || message.is_a?(Hash)
22
22
  message
23
23
  else
24
- { error: message }
24
+ { error: ensure_utf8(message) }
25
25
  end
26
26
  end
27
+
28
+ def ensure_utf8(message)
29
+ return message unless message.respond_to? :encode
30
+
31
+ message.encode('UTF-8', invalid: :replace, undef: :replace)
32
+ end
27
33
  end
28
34
  end
29
35
  end
@@ -7,11 +7,12 @@ module Grape
7
7
  BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'
8
8
  FALLBACK_LOCALE = :en
9
9
 
10
- attr_reader :status, :message, :headers
10
+ attr_reader :status, :headers
11
11
 
12
12
  def initialize(status: nil, message: nil, headers: nil, **_options)
13
+ super(message)
14
+
13
15
  @status = status
14
- @message = message
15
16
  @headers = headers
16
17
  end
17
18
 
@@ -2,10 +2,17 @@
2
2
 
3
3
  module Grape
4
4
  module Exceptions
5
- class MissingGroupTypeError < Base
5
+ class MissingGroupType < Base
6
6
  def initialize
7
7
  super(message: compose_message(:missing_group_type))
8
8
  end
9
9
  end
10
10
  end
11
11
  end
12
+
13
+ Grape::Exceptions::MissingGroupTypeError = Class.new(Grape::Exceptions::MissingGroupType) do
14
+ def initialize(*)
15
+ super
16
+ warn '[DEPRECATION] `Grape::Exceptions::MissingGroupTypeError` is deprecated. Use `Grape::Exceptions::MissingGroupType` instead.'
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class TooManyMultipartFiles < Base
6
+ def initialize(limit)
7
+ super(message: compose_message(:too_many_multipart_files, limit: limit), status: 413)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,10 +2,17 @@
2
2
 
3
3
  module Grape
4
4
  module Exceptions
5
- class UnsupportedGroupTypeError < Base
5
+ class UnsupportedGroupType < Base
6
6
  def initialize
7
7
  super(message: compose_message(:unsupported_group_type))
8
8
  end
9
9
  end
10
10
  end
11
11
  end
12
+
13
+ Grape::Exceptions::UnsupportedGroupTypeError = Class.new(Grape::Exceptions::UnsupportedGroupType) do
14
+ def initialize(*)
15
+ super
16
+ warn '[DEPRECATION] `Grape::Exceptions::UnsupportedGroupTypeError` is deprecated. Use `Grape::Exceptions::UnsupportedGroupType` instead.'
17
+ end
18
+ end
@@ -21,10 +21,6 @@ module Grape
21
21
  def as_json(*_args)
22
22
  to_s
23
23
  end
24
-
25
- def to_s
26
- message
27
- end
28
24
  end
29
25
  end
30
26
  end
@@ -11,8 +11,8 @@ en:
11
11
  except_values: 'has a value not allowed'
12
12
  same_as: 'is not the same as %{parameter}'
13
13
  missing_vendor_option:
14
- problem: 'missing :vendor option.'
15
- summary: 'when version using header, you must specify :vendor option. '
14
+ problem: 'missing :vendor option'
15
+ summary: 'when version using header, you must specify :vendor option'
16
16
  resolution: "eg: version 'v1', using: :header, vendor: 'twitter'"
17
17
  missing_mime_type:
18
18
  problem: 'missing mime type for %{new_format}'
@@ -21,12 +21,12 @@ en:
21
21
  or add your own with content_type :%{new_format}, 'application/%{new_format}'
22
22
  "
23
23
  invalid_with_option_for_represent:
24
- problem: 'You must specify an entity class in the :with option.'
24
+ problem: 'you must specify an entity class in the :with option'
25
25
  resolution: 'eg: represent User, :with => Entity::User'
26
- missing_option: 'You must specify :%{option} options.'
26
+ missing_option: 'you must specify :%{option} options'
27
27
  invalid_formatter: 'cannot convert %{klass} to %{to_format}'
28
28
  invalid_versioner_option:
29
- problem: 'Unknown :using for versioner: %{strategy}'
29
+ problem: 'unknown :using for versioner: %{strategy}'
30
30
  resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
31
31
  unknown_validator: 'unknown validator: %{validator_type}'
32
32
  unknown_options: 'unknown options: %{options}'
@@ -44,11 +44,12 @@ en:
44
44
  "when specifying %{body_format} as content-type, you must pass valid
45
45
  %{body_format} in the request's 'body'
46
46
  "
47
- empty_message_body: 'Empty message body supplied with %{body_format} content-type'
47
+ empty_message_body: 'empty message body supplied with %{body_format} content-type'
48
+ too_many_multipart_files: "the number of uploaded files exceeded the system's configured limit (%{limit})"
48
49
  invalid_accept_header:
49
- problem: 'Invalid accept header'
50
+ problem: 'invalid accept header'
50
51
  resolution: '%{message}'
51
52
  invalid_version_header:
52
- problem: 'Invalid version header'
53
+ problem: 'invalid version header'
53
54
  resolution: '%{message}'
54
55
  invalid_response: 'Invalid response'
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rack/auth/basic'
4
- require 'active_support/concern'
5
4
 
6
5
  module Grape
7
6
  module Middleware
@@ -72,7 +72,7 @@ module Grape
72
72
 
73
73
  def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
74
74
  message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
75
- Rack::Response.new([message], status, headers)
75
+ Rack::Response.new([message], Rack::Utils.status_code(status), headers)
76
76
  end
77
77
 
78
78
  def format_message(message, backtrace, original_exception = nil)
@@ -121,7 +121,7 @@ module Grape
121
121
 
122
122
  def run_rescue_handler(handler, error)
123
123
  if handler.instance_of?(Symbol)
124
- raise NoMethodError, "undefined method `#{handler}'" unless respond_to?(handler)
124
+ raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
125
125
 
126
126
  handler = public_method(handler)
127
127
  end
@@ -95,7 +95,7 @@ module Grape
95
95
 
96
96
  # @return [Rack::Builder] the builder object with our middlewares applied
97
97
  def build(builder = Rack::Builder.new)
98
- others.shift(others.size).each(&method(:merge_with))
98
+ others.shift(others.size).each { |m| merge_with(m) }
99
99
  middlewares.each do |m|
100
100
  m.use_in(builder)
101
101
  end
data/lib/grape/request.rb CHANGED
@@ -17,6 +17,8 @@ module Grape
17
17
  @params ||= build_params
18
18
  rescue EOFError
19
19
  raise Grape::Exceptions::EmptyMessageBody.new(content_type)
20
+ rescue Rack::Multipart::MultipartPartLimitError
21
+ raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
20
22
  end
21
23
 
22
24
  def headers
@@ -45,7 +47,7 @@ module Grape
45
47
  end
46
48
 
47
49
  def transform_header(header)
48
- -header[5..-1].split('_').each(&:capitalize!).join('-')
50
+ -header[5..].split('_').each(&:capitalize!).join('-')
49
51
  end
50
52
  end
51
53
  end
@@ -39,7 +39,7 @@ module Grape
39
39
 
40
40
  def method_missing(method_name, *args)
41
41
  if setter?(method_name[-1])
42
- attributes[method_name[0..-1]] = *args
42
+ attributes[method_name[0..]] = *args
43
43
  else
44
44
  attributes[method_name]
45
45
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # only exists to make it shorter for external use
4
+ module Grape
5
+ module Types
6
+ InvalidValue = Class.new(Grape::Validations::Types::InvalidValue)
7
+ end
8
+ end
@@ -1,4 +1,4 @@
1
- # frozen_String_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'singleton'
4
4
  require 'forwardable'
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Grape
4
6
  if Object.const_defined? :MultiJson
5
7
  Json = ::MultiJson
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ class ParamsScope
6
+ # Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
7
+ # internal API), the class only cleans up attributes to avoid junk in RAM.
8
+ class AttributesDoc
9
+ attr_accessor :type, :values
10
+
11
+ # @param api [Grape::API::Instance]
12
+ # @param scope [Validations::ParamsScope]
13
+ def initialize(api, scope)
14
+ @api = api
15
+ @scope = scope
16
+ @type = type
17
+ end
18
+
19
+ def extract_details(validations)
20
+ details[:required] = validations.key?(:presence)
21
+
22
+ desc = validations.delete(:desc) || validations.delete(:description)
23
+
24
+ details[:desc] = desc if desc
25
+
26
+ documentation = validations.delete(:documentation)
27
+
28
+ details[:documentation] = documentation if documentation
29
+
30
+ details[:default] = validations[:default] if validations.key?(:default)
31
+ end
32
+
33
+ def document(attrs)
34
+ return if @api.namespace_inheritable(:do_not_document)
35
+
36
+ details[:type] = type.to_s if type
37
+ details[:values] = values if values
38
+
39
+ documented_attrs = attrs.each_with_object({}) do |name, memo|
40
+ memo[@scope.full_name(name)] = details
41
+ end
42
+
43
+ @api.namespace_stackable(:params, documented_attrs)
44
+ end
45
+
46
+ def required
47
+ details[:required]
48
+ end
49
+
50
+ protected
51
+
52
+ def details
53
+ @details ||= {}
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'attributes_doc'
4
+
3
5
  module Grape
4
6
  module Validations
5
7
  class ParamsScope
@@ -8,6 +10,35 @@ module Grape
8
10
 
9
11
  include Grape::DSL::Parameters
10
12
 
13
+ class Attr
14
+ attr_accessor :key, :scope
15
+
16
+ # Open up a new ParamsScope::Attr
17
+ # @param key [Hash, Symbol] key of attr
18
+ # @param scope [Grape::Validations::ParamsScope] scope of attr
19
+ def initialize(key, scope)
20
+ @key = key
21
+ @scope = scope
22
+ end
23
+
24
+ # @return Array[Symbol, Hash[Symbol => Array]] declared_params with symbol instead of Attr
25
+ def self.attrs_keys(declared_params)
26
+ declared_params.map do |declared_param_attr|
27
+ attr_key(declared_param_attr)
28
+ end
29
+ end
30
+
31
+ def self.attr_key(declared_param_attr)
32
+ return attr_key(declared_param_attr.key) if declared_param_attr.is_a?(self)
33
+
34
+ if declared_param_attr.is_a?(Hash)
35
+ declared_param_attr.transform_values { |value| attrs_keys(value) }
36
+ else
37
+ declared_param_attr
38
+ end
39
+ end
40
+ end
41
+
11
42
  # Open up a new ParamsScope, allowing parameter definitions per
12
43
  # Grape::DSL::Params.
13
44
  # @param opts [Hash] options for this scope
@@ -31,7 +62,7 @@ module Grape
31
62
  @api = opts[:api]
32
63
  @optional = opts[:optional] || false
33
64
  @type = opts[:type]
34
- @group = opts[:group] || {}
65
+ @group = opts[:group]
35
66
  @dependent_on = opts[:dependent_on]
36
67
  @declared_params = []
37
68
  @index = nil
@@ -64,6 +95,18 @@ module Grape
64
95
 
65
96
  return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
66
97
 
98
+ meets_hash_dependency?(params)
99
+ end
100
+
101
+ def attr_meets_dependency?(params)
102
+ return true unless @dependent_on
103
+
104
+ return false if @parent.present? && !@parent.attr_meets_dependency?(params)
105
+
106
+ meets_hash_dependency?(params)
107
+ end
108
+
109
+ def meets_hash_dependency?(params)
67
110
  # params might be anything what looks like a hash, so it must implement a `key?` method
68
111
  return false unless params.respond_to?(:key?)
69
112
 
@@ -128,13 +171,14 @@ module Grape
128
171
  # Adds a parameter declaration to our list of validations.
129
172
  # @param attrs [Array] (see Grape::DSL::Parameters#requires)
130
173
  def push_declared_params(attrs, **opts)
174
+ opts = opts.merge(declared_params_scope: self) unless opts.key?(:declared_params_scope)
131
175
  if lateral?
132
176
  @parent.push_declared_params(attrs, **opts)
133
177
  else
134
178
  push_renamed_param(full_path + [attrs.first], opts[:as]) \
135
179
  if opts && opts[:as]
136
180
 
137
- @declared_params.concat attrs
181
+ @declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
138
182
  end
139
183
  end
140
184
 
@@ -206,8 +250,8 @@ module Grape
206
250
  # if required params are grouped and no type or unsupported type is provided, raise an error
207
251
  type = attrs[1] ? attrs[1][:type] : nil
208
252
  if attrs.first && !optional
209
- raise Grape::Exceptions::MissingGroupTypeError.new if type.nil?
210
- raise Grape::Exceptions::UnsupportedGroupTypeError.new unless Grape::Validations::Types.group?(type)
253
+ raise Grape::Exceptions::MissingGroupType if type.nil?
254
+ raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
211
255
  end
212
256
 
213
257
  self.class.new(
@@ -269,17 +313,14 @@ module Grape
269
313
  end
270
314
 
271
315
  def validates(attrs, validations)
272
- doc_attrs = { required: validations.key?(:presence) }
316
+ doc = AttributesDoc.new @api, self
317
+ doc.extract_details validations
273
318
 
274
319
  coerce_type = infer_coercion(validations)
275
320
 
276
- doc_attrs[:type] = coerce_type.to_s if coerce_type
277
-
278
- desc = validations.delete(:desc) || validations.delete(:description)
279
- doc_attrs[:desc] = desc if desc
321
+ doc.type = coerce_type
280
322
 
281
323
  default = validations[:default]
282
- doc_attrs[:default] = default if validations.key?(:default)
283
324
 
284
325
  if (values_hash = validations[:values]).is_a? Hash
285
326
  values = values_hash[:value]
@@ -288,7 +329,8 @@ module Grape
288
329
  else
289
330
  values = validations[:values]
290
331
  end
291
- doc_attrs[:values] = values if values
332
+
333
+ doc.values = values
292
334
 
293
335
  except_values = options_key?(:except_values, :value, validations) ? validations[:except_values][:value] : validations[:except_values]
294
336
 
@@ -304,28 +346,22 @@ module Grape
304
346
  # type should be compatible with values array, if both exist
305
347
  validate_value_coercion(coerce_type, values, except_values, excepts)
306
348
 
307
- doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
308
-
309
- document_attribute(attrs, doc_attrs)
349
+ doc.document attrs
310
350
 
311
351
  opts = derive_validator_options(validations)
312
352
 
313
- order_specific_validations = Set[:as]
314
-
315
353
  # Validate for presence before any other validators
316
- validates_presence(validations, attrs, doc_attrs, opts) do |validation_type|
317
- order_specific_validations << validation_type
318
- end
354
+ validates_presence(validations, attrs, doc, opts)
319
355
 
320
356
  # Before we run the rest of the validators, let's handle
321
357
  # whatever coercion so that we are working with correctly
322
358
  # type casted values
323
- coerce_type validations, attrs, doc_attrs, opts
359
+ coerce_type validations, attrs, doc, opts
324
360
 
325
361
  validations.each do |type, options|
326
- next if order_specific_validations.include?(type)
362
+ next if type == :as
327
363
 
328
- validate(type, options, attrs, doc_attrs, opts)
364
+ validate(type, options, attrs, doc, opts)
329
365
  end
330
366
  end
331
367
 
@@ -389,7 +425,7 @@ module Grape
389
425
  # composited from more than one +requires+/+optional+
390
426
  # parameter, and needs to be run before most other
391
427
  # validations.
392
- def coerce_type(validations, attrs, doc_attrs, opts)
428
+ def coerce_type(validations, attrs, doc, opts)
393
429
  check_coerce_with(validations)
394
430
 
395
431
  return unless validations.key?(:coerce)
@@ -399,7 +435,7 @@ module Grape
399
435
  method: validations[:coerce_with],
400
436
  message: validations[:coerce_message]
401
437
  }
402
- validate('coerce', coerce_options, attrs, doc_attrs, opts)
438
+ validate('coerce', coerce_options, attrs, doc, opts)
403
439
  validations.delete(:coerce_with)
404
440
  validations.delete(:coerce)
405
441
  validations.delete(:coerce_message)
@@ -430,18 +466,14 @@ module Grape
430
466
  unless Array(default).none? { |def_val| excepts.include?(def_val) }
431
467
  end
432
468
 
433
- def validate(type, options, attrs, doc_attrs, opts)
434
- validator_class = Validations.validators[type.to_s]
435
-
436
- raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
437
-
469
+ def validate(type, options, attrs, doc, opts)
438
470
  validator_options = {
439
471
  attributes: attrs,
440
472
  options: options,
441
- required: doc_attrs[:required],
473
+ required: doc.required,
442
474
  params_scope: self,
443
475
  opts: opts,
444
- validator_class: validator_class
476
+ validator_class: Validations.require_validator(type)
445
477
  }
446
478
  @api.namespace_stackable(:validations, validator_options)
447
479
  end
@@ -453,7 +485,7 @@ module Grape
453
485
  values_list.each do |values|
454
486
  next if !values || values.is_a?(Proc)
455
487
 
456
- value_types = values.is_a?(Range) ? [values.begin, values.end] : values
488
+ value_types = values.is_a?(Range) ? [values.begin, values.end].compact : values
457
489
  value_types = value_types.map { |type| Grape::API::Boolean.build(type) } if coerce_type == Grape::API::Boolean
458
490
  raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) unless value_types.all?(coerce_type)
459
491
  end
@@ -485,17 +517,11 @@ module Grape
485
517
  }
486
518
  end
487
519
 
488
- def validates_presence(validations, attrs, doc_attrs, opts)
520
+ def validates_presence(validations, attrs, doc, opts)
489
521
  return unless validations.key?(:presence) && validations[:presence]
490
522
 
491
- validate(:presence, validations[:presence], attrs, doc_attrs, opts)
492
- yield :presence
493
- yield :message if validations.key?(:message)
494
- end
495
-
496
- def document_attribute(attrs, doc_attrs)
497
- full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
498
- @api.document_attribute(full_attrs, doc_attrs)
523
+ validate(:presence, validations.delete(:presence), attrs, doc, opts)
524
+ validations.delete(:message) if validations.key?(:message)
499
525
  end
500
526
  end
501
527
  end
@@ -14,8 +14,6 @@ module Grape
14
14
  # behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
15
15
  # maintains Virtus behavior in coercing.
16
16
  class ArrayCoercer < DryTypeCoercer
17
- register_collection Array
18
-
19
17
  def initialize(type, strict = false)
20
18
  super
21
19