grape 1.3.3 → 1.6.2

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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -2
  3. data/CONTRIBUTING.md +2 -1
  4. data/README.md +135 -23
  5. data/UPGRADING.md +237 -46
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +34 -42
  8. data/lib/grape/api.rb +21 -16
  9. data/lib/grape/cookies.rb +2 -0
  10. data/lib/grape/dsl/callbacks.rb +1 -1
  11. data/lib/grape/dsl/desc.rb +3 -5
  12. data/lib/grape/dsl/headers.rb +5 -2
  13. data/lib/grape/dsl/helpers.rb +8 -5
  14. data/lib/grape/dsl/inside_route.rb +72 -53
  15. data/lib/grape/dsl/middleware.rb +4 -4
  16. data/lib/grape/dsl/parameters.rb +11 -7
  17. data/lib/grape/dsl/request_response.rb +9 -6
  18. data/lib/grape/dsl/routing.rb +8 -9
  19. data/lib/grape/dsl/settings.rb +5 -5
  20. data/lib/grape/dsl/validations.rb +18 -1
  21. data/lib/grape/eager_load.rb +1 -1
  22. data/lib/grape/endpoint.rb +29 -42
  23. data/lib/grape/error_formatter/json.rb +2 -6
  24. data/lib/grape/error_formatter/xml.rb +2 -6
  25. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  26. data/lib/grape/exceptions/validation.rb +2 -3
  27. data/lib/grape/exceptions/validation_errors.rb +1 -1
  28. data/lib/grape/formatter/json.rb +1 -0
  29. data/lib/grape/formatter/serializable_hash.rb +2 -1
  30. data/lib/grape/formatter/xml.rb +1 -0
  31. data/lib/grape/locale/en.yml +1 -1
  32. data/lib/grape/middleware/auth/base.rb +3 -3
  33. data/lib/grape/middleware/auth/dsl.rb +7 -1
  34. data/lib/grape/middleware/base.rb +6 -3
  35. data/lib/grape/middleware/error.rb +11 -13
  36. data/lib/grape/middleware/formatter.rb +7 -7
  37. data/lib/grape/middleware/stack.rb +10 -3
  38. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  39. data/lib/grape/middleware/versioner/header.rb +6 -4
  40. data/lib/grape/middleware/versioner/param.rb +1 -0
  41. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  42. data/lib/grape/middleware/versioner/path.rb +2 -0
  43. data/lib/grape/parser/json.rb +1 -1
  44. data/lib/grape/parser/xml.rb +1 -1
  45. data/lib/grape/path.rb +1 -0
  46. data/lib/grape/request.rb +4 -1
  47. data/lib/grape/router/attribute_translator.rb +3 -3
  48. data/lib/grape/router/pattern.rb +1 -1
  49. data/lib/grape/router/route.rb +2 -2
  50. data/lib/grape/router.rb +31 -30
  51. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  52. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  53. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  54. data/lib/grape/util/base_inheritable.rb +2 -2
  55. data/lib/grape/util/inheritable_setting.rb +1 -3
  56. data/lib/grape/util/lazy_value.rb +4 -2
  57. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  58. data/lib/grape/validations/attributes_iterator.rb +8 -0
  59. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  60. data/lib/grape/validations/params_scope.rb +97 -62
  61. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  62. data/lib/grape/validations/types/custom_type_coercer.rb +16 -3
  63. data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
  64. data/lib/grape/validations/types/invalid_value.rb +24 -0
  65. data/lib/grape/validations/types/json.rb +2 -1
  66. data/lib/grape/validations/types/primitive_coercer.rb +4 -5
  67. data/lib/grape/validations/types.rb +1 -4
  68. data/lib/grape/validations/validator_factory.rb +1 -1
  69. data/lib/grape/validations/validators/all_or_none.rb +8 -5
  70. data/lib/grape/validations/validators/allow_blank.rb +9 -7
  71. data/lib/grape/validations/validators/as.rb +6 -8
  72. data/lib/grape/validations/validators/at_least_one_of.rb +7 -4
  73. data/lib/grape/validations/validators/base.rb +74 -69
  74. data/lib/grape/validations/validators/coerce.rb +63 -76
  75. data/lib/grape/validations/validators/default.rb +36 -34
  76. data/lib/grape/validations/validators/exactly_one_of.rb +9 -6
  77. data/lib/grape/validations/validators/except_values.rb +13 -11
  78. data/lib/grape/validations/validators/multiple_params_base.rb +24 -19
  79. data/lib/grape/validations/validators/mutual_exclusion.rb +8 -5
  80. data/lib/grape/validations/validators/presence.rb +7 -4
  81. data/lib/grape/validations/validators/regexp.rb +8 -5
  82. data/lib/grape/validations/validators/same_as.rb +18 -15
  83. data/lib/grape/validations/validators/values.rb +61 -56
  84. data/lib/grape/validations.rb +6 -0
  85. data/lib/grape/version.rb +1 -1
  86. data/lib/grape.rb +7 -3
  87. data/spec/grape/api/custom_validations_spec.rb +77 -45
  88. data/spec/grape/api/deeply_included_options_spec.rb +3 -3
  89. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -1
  90. data/spec/grape/api/invalid_format_spec.rb +2 -0
  91. data/spec/grape/api/recognize_path_spec.rb +1 -1
  92. data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
  93. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -15
  94. data/spec/grape/api_remount_spec.rb +25 -19
  95. data/spec/grape/api_spec.rb +576 -211
  96. data/spec/grape/dsl/callbacks_spec.rb +2 -1
  97. data/spec/grape/dsl/headers_spec.rb +39 -9
  98. data/spec/grape/dsl/helpers_spec.rb +3 -2
  99. data/spec/grape/dsl/inside_route_spec.rb +185 -34
  100. data/spec/grape/dsl/logger_spec.rb +16 -18
  101. data/spec/grape/dsl/middleware_spec.rb +2 -1
  102. data/spec/grape/dsl/parameters_spec.rb +2 -0
  103. data/spec/grape/dsl/request_response_spec.rb +1 -0
  104. data/spec/grape/dsl/routing_spec.rb +10 -7
  105. data/spec/grape/endpoint/declared_spec.rb +848 -0
  106. data/spec/grape/endpoint_spec.rb +77 -589
  107. data/spec/grape/entity_spec.rb +29 -23
  108. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -0
  109. data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -22
  110. data/spec/grape/exceptions/validation_errors_spec.rb +13 -10
  111. data/spec/grape/exceptions/validation_spec.rb +5 -3
  112. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -7
  113. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -8
  114. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -8
  115. data/spec/grape/integration/rack_sendfile_spec.rb +13 -9
  116. data/spec/grape/loading_spec.rb +8 -8
  117. data/spec/grape/middleware/auth/dsl_spec.rb +15 -6
  118. data/spec/grape/middleware/auth/strategies_spec.rb +61 -21
  119. data/spec/grape/middleware/base_spec.rb +24 -15
  120. data/spec/grape/middleware/error_spec.rb +3 -3
  121. data/spec/grape/middleware/exception_spec.rb +111 -161
  122. data/spec/grape/middleware/formatter_spec.rb +28 -7
  123. data/spec/grape/middleware/globals_spec.rb +7 -4
  124. data/spec/grape/middleware/stack_spec.rb +15 -12
  125. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -1
  126. data/spec/grape/middleware/versioner/header_spec.rb +14 -13
  127. data/spec/grape/middleware/versioner/param_spec.rb +7 -1
  128. data/spec/grape/middleware/versioner/path_spec.rb +5 -1
  129. data/spec/grape/middleware/versioner_spec.rb +1 -1
  130. data/spec/grape/parser_spec.rb +4 -0
  131. data/spec/grape/path_spec.rb +52 -52
  132. data/spec/grape/presenters/presenter_spec.rb +7 -6
  133. data/spec/grape/request_spec.rb +6 -4
  134. data/spec/grape/util/inheritable_setting_spec.rb +7 -7
  135. data/spec/grape/util/inheritable_values_spec.rb +3 -2
  136. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -1
  137. data/spec/grape/util/stackable_values_spec.rb +7 -5
  138. data/spec/grape/validations/instance_behaivour_spec.rb +9 -10
  139. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +14 -3
  140. data/spec/grape/validations/params_scope_spec.rb +72 -10
  141. data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -6
  142. data/spec/grape/validations/types/primitive_coercer_spec.rb +63 -7
  143. data/spec/grape/validations/types_spec.rb +8 -8
  144. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -56
  145. data/spec/grape/validations/validators/allow_blank_spec.rb +136 -140
  146. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -56
  147. data/spec/grape/validations/validators/coerce_spec.rb +248 -33
  148. data/spec/grape/validations/validators/default_spec.rb +121 -78
  149. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
  150. data/spec/grape/validations/validators/except_values_spec.rb +4 -3
  151. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -77
  152. data/spec/grape/validations/validators/presence_spec.rb +16 -1
  153. data/spec/grape/validations/validators/regexp_spec.rb +25 -31
  154. data/spec/grape/validations/validators/same_as_spec.rb +14 -20
  155. data/spec/grape/validations/validators/values_spec.rb +183 -178
  156. data/spec/grape/validations_spec.rb +342 -29
  157. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  158. data/spec/integration/multi_json/json_spec.rb +1 -1
  159. data/spec/integration/multi_xml/xml_spec.rb +1 -1
  160. data/spec/shared/versioning_examples.rb +32 -29
  161. data/spec/spec_helper.rb +12 -12
  162. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  163. data/spec/support/chunks.rb +14 -0
  164. data/spec/support/versioned_helpers.rb +4 -6
  165. metadata +110 -102
@@ -50,7 +50,7 @@ module Grape
50
50
  # end
51
51
  #
52
52
  def desc(description, options = {}, &config_block)
53
- if block_given?
53
+ if config_block
54
54
  endpoint_configuration = if defined?(configuration)
55
55
  # When the instance is mounted - the configuration is executed on mount time
56
56
  if configuration.respond_to?(:evaluate)
@@ -68,9 +68,7 @@ module Grape
68
68
  end
69
69
 
70
70
  config_class.configure(&config_block)
71
- unless options.empty?
72
- warn '[DEPRECATION] Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.'
73
- end
71
+ warn '[DEPRECATION] Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.' unless options.empty?
74
72
  options = config_class.settings
75
73
  else
76
74
  options = options.merge(description: description)
@@ -92,7 +90,7 @@ module Grape
92
90
 
93
91
  def unset_description_field(field)
94
92
  description = route_setting(:description)
95
- description.delete(field) if description
93
+ description&.delete(field)
96
94
  end
97
95
 
98
96
  # Returns an object which configures itself via an instance-context DSL.
@@ -3,8 +3,11 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Headers
6
- # Set an individual header or retrieve
7
- # all headers that have been set.
6
+ # This method has four responsibilities:
7
+ # 1. Set a specifc header value by key
8
+ # 2. Retrieve a specifc header value by key
9
+ # 3. Retrieve all headers that have been set
10
+ # 4. Delete a specifc header key-value pair
8
11
  def header(key = nil, val = nil)
9
12
  if key
10
13
  val ? header[key.to_s] = val : header.delete(key.to_s)
@@ -36,8 +36,8 @@ module Grape
36
36
  #
37
37
  def helpers(*new_modules, &block)
38
38
  include_new_modules(new_modules) if new_modules.any?
39
- include_block(block) if block_given?
40
- include_all_in_scope if !block_given? && new_modules.empty?
39
+ include_block(block) if block
40
+ include_all_in_scope if !block && new_modules.empty?
41
41
  end
42
42
 
43
43
  protected
@@ -67,12 +67,13 @@ module Grape
67
67
 
68
68
  def define_boolean_in_mod(mod)
69
69
  return if defined? mod::Boolean
70
- mod.const_set('Boolean', Grape::API::Boolean)
70
+
71
+ mod.const_set(:Boolean, Grape::API::Boolean)
71
72
  end
72
73
 
73
- def inject_api_helpers_to_mod(mod, &_block)
74
+ def inject_api_helpers_to_mod(mod, &block)
74
75
  mod.extend(BaseHelper) unless mod.is_a?(BaseHelper)
75
- yield if block_given?
76
+ yield if block
76
77
  mod.api_changed(self)
77
78
  end
78
79
  end
@@ -81,6 +82,7 @@ module Grape
81
82
  # to provide some API-specific functionality.
82
83
  module BaseHelper
83
84
  attr_accessor :api
85
+
84
86
  def params(name, &block)
85
87
  @named_params ||= {}
86
88
  @named_params[name] = block
@@ -95,6 +97,7 @@ module Grape
95
97
 
96
98
  def process_named_params
97
99
  return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
100
+
98
101
  api.namespace_stackable(:named_params, @named_params)
99
102
  end
100
103
  end
@@ -48,6 +48,8 @@ module Grape
48
48
  end
49
49
 
50
50
  def declared_hash(passed_params, options, declared_params, params_nested_path)
51
+ renamed_params = route_setting(:renamed_params) || {}
52
+
51
53
  declared_params.each_with_object(passed_params.class.new) do |declared_param, memo|
52
54
  if declared_param.is_a?(Hash)
53
55
  declared_param.each_pair do |declared_parent_param, declared_children_params|
@@ -55,82 +57,72 @@ module Grape
55
57
  params_nested_path_dup << declared_parent_param.to_s
56
58
  next unless options[:include_missing] || passed_params.key?(declared_parent_param)
57
59
 
60
+ rename_path = params_nested_path + [declared_parent_param.to_s]
61
+ renamed_param_name = renamed_params[rename_path]
62
+
63
+ memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
58
64
  passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
59
- memo_key = optioned_param_key(declared_parent_param, options)
60
65
 
61
- memo[memo_key] = handle_passed_param(passed_children_params, params_nested_path_dup) do
66
+ memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
62
67
  declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
63
68
  end
64
69
  end
65
70
  else
66
71
  # If it is not a Hash then it does not have children.
67
72
  # Find its value or set it to nil.
68
- has_renaming = route_setting(:renamed_params) && route_setting(:renamed_params).find { |current| current[declared_param] }
69
- param_renaming = has_renaming[declared_param] if has_renaming
73
+ next unless options[:include_missing] || passed_params.key?(declared_param)
74
+
75
+ rename_path = params_nested_path + [declared_param.to_s]
76
+ renamed_param_name = renamed_params[rename_path]
70
77
 
71
- next unless options[:include_missing] || passed_params.key?(declared_param) || (param_renaming && passed_params.key?(param_renaming))
78
+ memo_key = optioned_param_key(renamed_param_name || declared_param, options)
79
+ passed_param = passed_params[declared_param]
72
80
 
73
- if param_renaming
74
- memo[optioned_param_key(param_renaming, options)] = passed_params[param_renaming]
75
- else
76
- memo[optioned_param_key(declared_param, options)] = passed_params[declared_param]
81
+ params_nested_path_dup = params_nested_path.dup
82
+ params_nested_path_dup << declared_param.to_s
83
+ memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
84
+ passed_param
77
85
  end
78
86
  end
79
87
  end
80
88
  end
81
89
 
82
- def handle_passed_param(passed_children_params, params_nested_path, &_block)
83
- if should_be_empty_hash?(passed_children_params, params_nested_path)
90
+ def handle_passed_param(params_nested_path, has_passed_children = false, &_block)
91
+ return yield if has_passed_children
92
+
93
+ key = params_nested_path[0]
94
+ key += "[#{params_nested_path[1..-1].join('][')}]" if params_nested_path.size > 1
95
+
96
+ route_options_params = options[:route_options][:params] || {}
97
+ type = route_options_params.dig(key, :type)
98
+ has_children = route_options_params.keys.any? { |k| k != key && k.start_with?(key) }
99
+
100
+ if type == 'Hash' && !has_children
84
101
  {}
85
- elsif should_be_empty_array?(passed_children_params, params_nested_path)
102
+ elsif type == 'Array' || (type&.start_with?('[') && !type&.include?(','))
86
103
  []
104
+ elsif type == 'Set' || type&.start_with?('#<Set')
105
+ Set.new
87
106
  else
88
107
  yield
89
108
  end
90
109
  end
91
110
 
92
- def should_be_empty_array?(passed_children_params, params_nested_path)
93
- passed_children_params.empty? && declared_param_is_array?(params_nested_path)
94
- end
95
-
96
- def declared_param_is_array?(params_nested_path)
97
- key = route_options_params_key(params_nested_path)
98
- route_options_params[key] && route_options_params[key][:type] == 'Array'
99
- end
100
-
101
- def should_be_empty_hash?(passed_children_params, params_nested_path)
102
- passed_children_params.empty? && declared_param_is_hash?(params_nested_path)
103
- end
104
-
105
- def declared_param_is_hash?(params_nested_path)
106
- key = route_options_params_key(params_nested_path)
107
- route_options_params[key] && route_options_params[key][:type] == 'Hash'
108
- end
109
-
110
- def route_options_params
111
- options[:route_options][:params] || {}
112
- end
113
-
114
111
  def optioned_param_key(declared_param, options)
115
112
  options[:stringify] ? declared_param.to_s : declared_param.to_sym
116
113
  end
117
114
 
118
- def route_options_params_key(params_nested_path)
119
- key = params_nested_path[0]
120
- key += '[' + params_nested_path[1..-1].join('][') + ']' if params_nested_path.size > 1
121
- key
122
- end
123
-
124
115
  def optioned_declared_params(**options)
125
116
  declared_params = if options[:include_parent_namespaces]
126
117
  # Declared params including parent namespaces
127
- route_setting(:saved_declared_params).flatten | Array(route_setting(:declared_params))
118
+ route_setting(:declared_params)
128
119
  else
129
120
  # Declared params at current namespace
130
- route_setting(:saved_declared_params).last & Array(route_setting(:declared_params))
121
+ namespace_stackable(:declared_params).last || []
131
122
  end
132
123
 
133
124
  raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
125
+
134
126
  declared_params
135
127
  end
136
128
  end
@@ -201,11 +193,13 @@ module Grape
201
193
  case status
202
194
  when Symbol
203
195
  raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
196
+
204
197
  @status = Rack::Utils.status_code(status)
205
198
  when Integer
206
199
  @status = status
207
200
  when nil
208
201
  return @status if instance_variable_defined?(:@status) && @status
202
+
209
203
  case request.request_method.to_s.upcase
210
204
  when Grape::Http::Headers::POST
211
205
  201
@@ -279,23 +273,36 @@ module Grape
279
273
  body false
280
274
  end
281
275
 
282
- # Allows you to define the response as a file-like object.
276
+ # Deprecated method to send files to the client. Use `sendfile` or `stream`
277
+ def file(value = nil)
278
+ if value.is_a?(String)
279
+ warn '[DEPRECATION] Use sendfile or stream to send files.'
280
+ sendfile(value)
281
+ elsif !value.is_a?(NilClass)
282
+ warn '[DEPRECATION] Use stream to use a Stream object.'
283
+ stream(value)
284
+ else
285
+ warn '[DEPRECATION] Use sendfile or stream to send files.'
286
+ sendfile
287
+ end
288
+ end
289
+
290
+ # Allows you to send a file to the client via sendfile.
283
291
  #
284
292
  # @example
285
293
  # get '/file' do
286
- # file FileStreamer.new(...)
294
+ # sendfile FileStreamer.new(...)
287
295
  # end
288
296
  #
289
297
  # GET /file # => "contents of file"
290
- def file(value = nil)
298
+ def sendfile(value = nil)
291
299
  if value.is_a?(String)
292
- file_body = Grape::ServeFile::FileBody.new(value)
293
- @file = Grape::ServeFile::FileResponse.new(file_body)
300
+ file_body = Grape::ServeStream::FileBody.new(value)
301
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
294
302
  elsif !value.is_a?(NilClass)
295
- warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.'
296
- @file = Grape::ServeFile::FileResponse.new(value)
303
+ raise ArgumentError, 'Argument must be a file path'
297
304
  else
298
- instance_variable_defined?(:@file) ? @file : nil
305
+ stream
299
306
  end
300
307
  end
301
308
 
@@ -315,10 +322,21 @@ module Grape
315
322
  # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb
316
323
  # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb
317
324
  def stream(value = nil)
325
+ return if value.nil? && @stream.nil?
326
+
318
327
  header 'Content-Length', nil
319
328
  header 'Transfer-Encoding', nil
320
329
  header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
321
- file(value)
330
+ if value.is_a?(String)
331
+ file_body = Grape::ServeStream::FileBody.new(value)
332
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
333
+ elsif value.respond_to?(:each)
334
+ @stream = Grape::ServeStream::StreamResponse.new(value)
335
+ elsif !value.is_a?(NilClass)
336
+ raise ArgumentError, 'Stream object must respond to :each.'
337
+ else
338
+ @stream
339
+ end
322
340
  end
323
341
 
324
342
  # Allows you to make use of Grape Entities by setting
@@ -359,6 +377,7 @@ module Grape
359
377
  representation = (body || {}).merge(key => representation)
360
378
  elsif entity_class.present? && body
361
379
  raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
380
+
362
381
  representation = body.merge(representation)
363
382
  end
364
383
 
@@ -389,7 +408,7 @@ module Grape
389
408
  entity_class = options.delete(:with)
390
409
 
391
410
  if entity_class.nil?
392
- # entity class not explicitely defined, auto-detect from relation#klass or first object in the collection
411
+ # entity class not explicitly defined, auto-detect from relation#klass or first object in the collection
393
412
  object_class = if object.respond_to?(:klass)
394
413
  object.klass
395
414
  else
@@ -411,7 +430,7 @@ module Grape
411
430
  def entity_representation_for(entity_class, object, options)
412
431
  embeds = { env: env }
413
432
  embeds[:version] = env[Grape::Env::API_VERSION] if env[Grape::Env::API_VERSION]
414
- entity_class.represent(object, embeds.merge(options))
433
+ entity_class.represent(object, **embeds.merge(options))
415
434
  end
416
435
  end
417
436
  end
@@ -18,28 +18,28 @@ module Grape
18
18
  # to inject.
19
19
  def use(middleware_class, *args, &block)
20
20
  arr = [:use, middleware_class, *args]
21
- arr << block if block_given?
21
+ arr << block if block
22
22
 
23
23
  namespace_stackable(:middleware, arr)
24
24
  end
25
25
 
26
26
  def insert(*args, &block)
27
27
  arr = [:insert, *args]
28
- arr << block if block_given?
28
+ arr << block if block
29
29
 
30
30
  namespace_stackable(:middleware, arr)
31
31
  end
32
32
 
33
33
  def insert_before(*args, &block)
34
34
  arr = [:insert_before, *args]
35
- arr << block if block_given?
35
+ arr << block if block
36
36
 
37
37
  namespace_stackable(:middleware, arr)
38
38
  end
39
39
 
40
40
  def insert_after(*args, &block)
41
41
  arr = [:insert_after, *args]
42
- arr << block if block_given?
42
+ arr << block if block
43
43
 
44
44
  namespace_stackable(:middleware, arr)
45
45
  end
@@ -72,7 +72,7 @@ module Grape
72
72
 
73
73
  # Require one or more parameters for the current endpoint.
74
74
  #
75
- # @param attrs list of parameter names, or, if :using is
75
+ # @param attrs list of parameters names, or, if :using is
76
76
  # passed as an option, which keys to include (:all or :none) from
77
77
  # the :using hash. The last key can be a hash, which specifies
78
78
  # options for the parameters
@@ -133,7 +133,7 @@ module Grape
133
133
  require_required_and_optional_fields(attrs.first, opts)
134
134
  else
135
135
  validate_attributes(attrs, opts, &block)
136
- block_given? ? new_scope(orig_attrs, &block) : push_declared_params(attrs, **opts.slice(:as))
136
+ block ? new_scope(orig_attrs, &block) : push_declared_params(attrs, **opts.slice(:as))
137
137
  end
138
138
  end
139
139
 
@@ -149,7 +149,7 @@ module Grape
149
149
  opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group
150
150
 
151
151
  # check type for optional parameter group
152
- if attrs && block_given?
152
+ if attrs && block
153
153
  raise Grape::Exceptions::MissingGroupTypeError.new if type.nil?
154
154
  raise Grape::Exceptions::UnsupportedGroupTypeError.new unless Grape::Validations::Types.group?(type)
155
155
  end
@@ -159,7 +159,7 @@ module Grape
159
159
  else
160
160
  validate_attributes(attrs, opts, &block)
161
161
 
162
- block_given? ? new_scope(orig_attrs, true, &block) : push_declared_params(attrs, **opts.slice(:as))
162
+ block ? new_scope(orig_attrs, true, &block) : push_declared_params(attrs, **opts.slice(:as))
163
163
  end
164
164
  end
165
165
 
@@ -227,13 +227,17 @@ module Grape
227
227
 
228
228
  alias group requires
229
229
 
230
- def map_params(params, element)
230
+ class EmptyOptionalValue; end
231
+
232
+ def map_params(params, element, is_array = false)
231
233
  if params.is_a?(Array)
232
234
  params.map do |el|
233
- map_params(el, element)
235
+ map_params(el, element, true)
234
236
  end
235
237
  elsif params.is_a?(Hash)
236
- params[element] || {}
238
+ params[element] || (@optional && is_array ? EmptyOptionalValue : {})
239
+ elsif params == EmptyOptionalValue
240
+ EmptyOptionalValue
237
241
  else
238
242
  {}
239
243
  end
@@ -26,6 +26,7 @@ module Grape
26
26
  # define a single mime type
27
27
  mime_type = content_types[new_format.to_sym]
28
28
  raise Grape::Exceptions::MissingMimeType.new(new_format) unless mime_type
29
+
29
30
  namespace_stackable(:content_types, new_format.to_sym => mime_type)
30
31
  else
31
32
  namespace_inheritable(:format)
@@ -102,14 +103,13 @@ module Grape
102
103
  def rescue_from(*args, &block)
103
104
  if args.last.is_a?(Proc)
104
105
  handler = args.pop
105
- elsif block_given?
106
+ elsif block
106
107
  handler = block
107
108
  end
108
109
 
109
110
  options = args.extract_options!
110
- if block_given? && options.key?(:with)
111
- raise ArgumentError, 'both :with option and block cannot be passed'
112
- end
111
+ raise ArgumentError, 'both :with option and block cannot be passed' if block && options.key?(:with)
112
+
113
113
  handler ||= extract_with(options)
114
114
 
115
115
  if args.include?(:all)
@@ -127,7 +127,7 @@ module Grape
127
127
  :base_only_rescue_handlers
128
128
  end
129
129
 
130
- namespace_reverse_stackable handler_type, Hash[args.map { |arg| [arg, handler] }]
130
+ namespace_reverse_stackable handler_type, args.map { |arg| [arg, handler] }.to_h
131
131
  end
132
132
 
133
133
  namespace_stackable(:rescue_options, options)
@@ -154,7 +154,8 @@ module Grape
154
154
  # @param model_class [Class] The model class that will be represented.
155
155
  # @option options [Class] :with The entity class that will represent the model.
156
156
  def represent(model_class, options)
157
- raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless options[:with] && options[:with].is_a?(Class)
157
+ raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless options[:with].is_a?(Class)
158
+
158
159
  namespace_stackable(:representations, model_class => options[:with])
159
160
  end
160
161
 
@@ -162,9 +163,11 @@ module Grape
162
163
 
163
164
  def extract_with(options)
164
165
  return unless options.key?(:with)
166
+
165
167
  with_option = options.delete(:with)
166
168
  return with_option if with_option.instance_of?(Proc)
167
169
  return with_option.to_sym if with_option.instance_of?(Symbol) || with_option.instance_of?(String)
170
+
168
171
  raise ArgumentError, "with: #{with_option.class}, expected Symbol, String or Proc"
169
172
  end
170
173
  end
@@ -38,7 +38,7 @@ module Grape
38
38
 
39
39
  @versions = versions | requested_versions
40
40
 
41
- if block_given?
41
+ if block
42
42
  within_namespace do
43
43
  namespace_inheritable(:version, requested_versions)
44
44
  namespace_inheritable(:version_options, options)
@@ -79,11 +79,12 @@ module Grape
79
79
  namespace_inheritable(:do_not_route_options, true)
80
80
  end
81
81
 
82
- def mount(mounts, opts = {})
82
+ def mount(mounts, *opts)
83
83
  mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
84
84
  mounts.each_pair do |app, path|
85
85
  if app.respond_to?(:mount_instance)
86
- mount(app.mount_instance(configuration: opts[:with] || {}) => path)
86
+ opts_with = opts.any? ? opts.shift[:with] : {}
87
+ mount({ app.mount_instance(configuration: opts_with) => path })
87
88
  next
88
89
  end
89
90
  in_setting = inheritable_setting
@@ -151,7 +152,7 @@ module Grape
151
152
  end
152
153
 
153
154
  # Declare a "namespace", which prefixes all subordinate routes with its
154
- # name. Any endpoints within a namespace, or group, resource, segment,
155
+ # name. Any endpoints within a namespace, group, resource or segment,
155
156
  # etc., will share their parent context as well as any configuration
156
157
  # done in the namespace context.
157
158
  #
@@ -165,14 +166,12 @@ module Grape
165
166
  def namespace(space = nil, options = {}, &block)
166
167
  @namespace_description = nil unless instance_variable_defined?(:@namespace_description) && @namespace_description
167
168
 
168
- if space || block_given?
169
+ if space || block
169
170
  within_namespace do
170
171
  previous_namespace_description = @namespace_description
171
172
  @namespace_description = (@namespace_description || {}).deep_merge(namespace_setting(:description) || {})
172
173
  nest(block) do
173
- if space
174
- namespace_stackable(:namespace, Namespace.new(space, **options))
175
- end
174
+ namespace_stackable(:namespace, Namespace.new(space, **options)) if space
176
175
  end
177
176
  @namespace_description = previous_namespace_description
178
177
  end
@@ -201,7 +200,7 @@ module Grape
201
200
  @endpoints = []
202
201
  end
203
202
 
204
- # Thie method allows you to quickly define a parameter route segment
203
+ # This method allows you to quickly define a parameter route segment
205
204
  # in your API.
206
205
  #
207
206
  # @param param [Symbol] The name of the parameter you wish to declare.
@@ -103,12 +103,14 @@ module Grape
103
103
  def namespace_stackable_with_hash(key)
104
104
  settings = get_or_set :namespace_stackable, key, nil
105
105
  return if settings.blank?
106
+
106
107
  settings.each_with_object({}) { |value, result| result.deep_merge!(value) }
107
108
  end
108
109
 
109
110
  def namespace_reverse_stackable_with_hash(key)
110
111
  settings = get_or_set :namespace_reverse_stackable, key, nil
111
112
  return if settings.blank?
113
+
112
114
  result = {}
113
115
  settings.each do |setting|
114
116
  setting.each do |field, value|
@@ -154,10 +156,10 @@ module Grape
154
156
 
155
157
  # Execute the block within a context where our inheritable settings are forked
156
158
  # to a new copy (see #namespace_start).
157
- def within_namespace(&_block)
159
+ def within_namespace(&block)
158
160
  namespace_start
159
161
 
160
- result = yield if block_given?
162
+ result = yield if block
161
163
 
162
164
  namespace_end
163
165
  reset_validations!
@@ -175,9 +177,7 @@ module Grape
175
177
  # +inheritable_setting+, however, it doesn't contain any user-defined settings.
176
178
  # Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+
177
179
  # in the chain for every endpoint.
178
- if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance
179
- setting.inherit_from superclass.inheritable_setting
180
- end
180
+ setting.inherit_from superclass.inheritable_setting if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance
181
181
  end
182
182
  end
183
183
  end
@@ -10,7 +10,24 @@ module Grape
10
10
  include Grape::DSL::Configuration
11
11
 
12
12
  module ClassMethods
13
- # Clears all defined parameters and validations.
13
+ # Clears all defined parameters and validations. The main purpose of it is to clean up
14
+ # settings, so next endpoint won't interfere with previous one.
15
+ #
16
+ # params do
17
+ # # params for the endpoint below this block
18
+ # end
19
+ # post '/current' do
20
+ # # whatever
21
+ # end
22
+ #
23
+ # # somewhere between them the reset_validations! method gets called
24
+ #
25
+ # params do
26
+ # # params for the endpoint below this block
27
+ # end
28
+ # post '/next' do
29
+ # # whatever
30
+ # end
14
31
  def reset_validations!
15
32
  unset_namespace_stackable :declared_params
16
33
  unset_namespace_stackable :validations
@@ -16,5 +16,5 @@ Grape::Parser.eager_load!
16
16
  Grape::DSL.eager_load!
17
17
  Grape::API.eager_load!
18
18
  Grape::Presenters.eager_load!
19
- Grape::ServeFile.eager_load!
19
+ Grape::ServeStream.eager_load!
20
20
  Rack::Head # AutoLoads the Rack::Head