grape 1.6.2 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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