grape 1.3.3 → 1.6.2

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