grape 1.2.5 → 1.4.0

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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -0
  3. data/LICENSE +1 -1
  4. data/README.md +53 -16
  5. data/UPGRADING.md +231 -23
  6. data/grape.gemspec +10 -1
  7. data/lib/grape.rb +6 -7
  8. data/lib/grape/api.rb +4 -2
  9. data/lib/grape/api/helpers.rb +2 -0
  10. data/lib/grape/api/instance.rb +36 -33
  11. data/lib/grape/config.rb +2 -0
  12. data/lib/grape/content_types.rb +34 -0
  13. data/lib/grape/cookies.rb +2 -0
  14. data/lib/grape/dsl/api.rb +2 -0
  15. data/lib/grape/dsl/callbacks.rb +2 -0
  16. data/lib/grape/dsl/configuration.rb +2 -0
  17. data/lib/grape/dsl/desc.rb +2 -0
  18. data/lib/grape/dsl/headers.rb +2 -0
  19. data/lib/grape/dsl/helpers.rb +4 -2
  20. data/lib/grape/dsl/inside_route.rb +83 -34
  21. data/lib/grape/dsl/logger.rb +2 -0
  22. data/lib/grape/dsl/middleware.rb +2 -0
  23. data/lib/grape/dsl/parameters.rb +8 -6
  24. data/lib/grape/dsl/request_response.rb +4 -2
  25. data/lib/grape/dsl/routing.rb +9 -5
  26. data/lib/grape/dsl/settings.rb +7 -1
  27. data/lib/grape/dsl/validations.rb +20 -1
  28. data/lib/grape/eager_load.rb +3 -1
  29. data/lib/grape/endpoint.rb +21 -13
  30. data/lib/grape/error_formatter.rb +3 -1
  31. data/lib/grape/error_formatter/base.rb +2 -0
  32. data/lib/grape/error_formatter/json.rb +2 -0
  33. data/lib/grape/error_formatter/txt.rb +2 -0
  34. data/lib/grape/error_formatter/xml.rb +2 -0
  35. data/lib/grape/exceptions/base.rb +11 -13
  36. data/lib/grape/exceptions/incompatible_option_values.rb +2 -0
  37. data/lib/grape/exceptions/invalid_accept_header.rb +2 -0
  38. data/lib/grape/exceptions/invalid_formatter.rb +2 -0
  39. data/lib/grape/exceptions/invalid_message_body.rb +2 -0
  40. data/lib/grape/exceptions/invalid_response.rb +2 -0
  41. data/lib/grape/exceptions/invalid_version_header.rb +2 -0
  42. data/lib/grape/exceptions/invalid_versioner_option.rb +2 -0
  43. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -0
  44. data/lib/grape/exceptions/method_not_allowed.rb +2 -0
  45. data/lib/grape/exceptions/missing_group_type.rb +2 -0
  46. data/lib/grape/exceptions/missing_mime_type.rb +2 -0
  47. data/lib/grape/exceptions/missing_option.rb +2 -0
  48. data/lib/grape/exceptions/missing_vendor_option.rb +2 -0
  49. data/lib/grape/exceptions/unknown_options.rb +2 -0
  50. data/lib/grape/exceptions/unknown_parameter.rb +2 -0
  51. data/lib/grape/exceptions/unknown_validator.rb +2 -0
  52. data/lib/grape/exceptions/unsupported_group_type.rb +2 -0
  53. data/lib/grape/exceptions/validation.rb +3 -1
  54. data/lib/grape/exceptions/validation_array_errors.rb +2 -0
  55. data/lib/grape/exceptions/validation_errors.rb +13 -12
  56. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
  57. data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
  58. data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
  59. data/lib/grape/extensions/hash.rb +2 -0
  60. data/lib/grape/extensions/hashie/mash.rb +2 -0
  61. data/lib/grape/formatter.rb +5 -3
  62. data/lib/grape/formatter/json.rb +2 -0
  63. data/lib/grape/formatter/serializable_hash.rb +2 -0
  64. data/lib/grape/formatter/txt.rb +2 -0
  65. data/lib/grape/formatter/xml.rb +2 -0
  66. data/lib/grape/http/headers.rb +50 -18
  67. data/lib/grape/middleware/auth/base.rb +2 -0
  68. data/lib/grape/middleware/auth/dsl.rb +2 -0
  69. data/lib/grape/middleware/auth/strategies.rb +2 -0
  70. data/lib/grape/middleware/auth/strategy_info.rb +2 -0
  71. data/lib/grape/middleware/base.rb +7 -7
  72. data/lib/grape/middleware/error.rb +3 -1
  73. data/lib/grape/middleware/filter.rb +2 -0
  74. data/lib/grape/middleware/formatter.rb +8 -6
  75. data/lib/grape/middleware/globals.rb +2 -0
  76. data/lib/grape/middleware/helpers.rb +2 -0
  77. data/lib/grape/middleware/stack.rb +4 -1
  78. data/lib/grape/middleware/versioner.rb +2 -0
  79. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -0
  80. data/lib/grape/middleware/versioner/header.rb +6 -4
  81. data/lib/grape/middleware/versioner/param.rb +3 -1
  82. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +4 -1
  83. data/lib/grape/middleware/versioner/path.rb +3 -1
  84. data/lib/grape/namespace.rb +14 -2
  85. data/lib/grape/parser.rb +3 -1
  86. data/lib/grape/parser/json.rb +2 -0
  87. data/lib/grape/parser/xml.rb +2 -0
  88. data/lib/grape/path.rb +15 -3
  89. data/lib/grape/presenters/presenter.rb +2 -0
  90. data/lib/grape/request.rb +15 -8
  91. data/lib/grape/router.rb +30 -29
  92. data/lib/grape/router/attribute_translator.rb +39 -8
  93. data/lib/grape/router/pattern.rb +20 -16
  94. data/lib/grape/router/route.rb +12 -26
  95. data/lib/grape/{serve_file → serve_stream}/file_body.rb +3 -1
  96. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +3 -1
  97. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +10 -8
  98. data/lib/grape/util/base_inheritable.rb +15 -6
  99. data/lib/grape/util/cache.rb +20 -0
  100. data/lib/grape/util/endpoint_configuration.rb +2 -0
  101. data/lib/grape/util/env.rb +19 -17
  102. data/lib/grape/util/inheritable_setting.rb +2 -0
  103. data/lib/grape/util/inheritable_values.rb +2 -0
  104. data/lib/grape/util/json.rb +2 -0
  105. data/lib/grape/util/lazy_block.rb +2 -0
  106. data/lib/grape/util/lazy_object.rb +43 -0
  107. data/lib/grape/util/lazy_value.rb +2 -0
  108. data/lib/grape/util/registrable.rb +2 -0
  109. data/lib/grape/util/reverse_stackable_values.rb +4 -0
  110. data/lib/grape/util/stackable_values.rb +10 -20
  111. data/lib/grape/util/strict_hash_configuration.rb +2 -0
  112. data/lib/grape/util/xml.rb +2 -0
  113. data/lib/grape/validations.rb +2 -0
  114. data/lib/grape/validations/attributes_iterator.rb +3 -3
  115. data/lib/grape/validations/multiple_attributes_iterator.rb +2 -0
  116. data/lib/grape/validations/params_scope.rb +27 -14
  117. data/lib/grape/validations/single_attribute_iterator.rb +13 -2
  118. data/lib/grape/validations/types.rb +12 -34
  119. data/lib/grape/validations/types/array_coercer.rb +65 -0
  120. data/lib/grape/validations/types/build_coercer.rb +47 -49
  121. data/lib/grape/validations/types/custom_type_coercer.rb +15 -49
  122. data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
  123. data/lib/grape/validations/types/dry_type_coercer.rb +76 -0
  124. data/lib/grape/validations/types/file.rb +22 -18
  125. data/lib/grape/validations/types/json.rb +46 -39
  126. data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
  127. data/lib/grape/validations/types/primitive_coercer.rb +67 -0
  128. data/lib/grape/validations/types/set_coercer.rb +40 -0
  129. data/lib/grape/validations/types/variant_collection_coercer.rb +5 -13
  130. data/lib/grape/validations/validator_factory.rb +2 -0
  131. data/lib/grape/validations/validators/all_or_none.rb +3 -1
  132. data/lib/grape/validations/validators/allow_blank.rb +3 -1
  133. data/lib/grape/validations/validators/as.rb +2 -0
  134. data/lib/grape/validations/validators/at_least_one_of.rb +3 -1
  135. data/lib/grape/validations/validators/base.rb +8 -5
  136. data/lib/grape/validations/validators/coerce.rb +39 -29
  137. data/lib/grape/validations/validators/default.rb +2 -1
  138. data/lib/grape/validations/validators/exactly_one_of.rb +6 -2
  139. data/lib/grape/validations/validators/except_values.rb +3 -1
  140. data/lib/grape/validations/validators/multiple_params_base.rb +2 -0
  141. data/lib/grape/validations/validators/mutual_exclusion.rb +3 -1
  142. data/lib/grape/validations/validators/presence.rb +3 -1
  143. data/lib/grape/validations/validators/regexp.rb +4 -2
  144. data/lib/grape/validations/validators/same_as.rb +6 -3
  145. data/lib/grape/validations/validators/values.rb +17 -5
  146. data/lib/grape/version.rb +3 -1
  147. data/spec/grape/api/custom_validations_spec.rb +5 -3
  148. data/spec/grape/api/deeply_included_options_spec.rb +2 -0
  149. data/spec/grape/api/defines_boolean_in_params_spec.rb +5 -3
  150. data/spec/grape/api/inherited_helpers_spec.rb +2 -0
  151. data/spec/grape/api/instance_spec.rb +104 -0
  152. data/spec/grape/api/invalid_format_spec.rb +2 -0
  153. data/spec/grape/api/namespace_parameters_in_route_spec.rb +2 -0
  154. data/spec/grape/api/nested_helpers_spec.rb +2 -0
  155. data/spec/grape/api/optional_parameters_in_route_spec.rb +2 -0
  156. data/spec/grape/api/parameters_modification_spec.rb +3 -1
  157. data/spec/grape/api/patch_method_helpers_spec.rb +2 -0
  158. data/spec/grape/api/recognize_path_spec.rb +2 -0
  159. data/spec/grape/api/required_parameters_in_route_spec.rb +2 -0
  160. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +2 -0
  161. data/spec/grape/api/routes_with_requirements_spec.rb +2 -0
  162. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +2 -0
  163. data/spec/grape/api/shared_helpers_spec.rb +2 -0
  164. data/spec/grape/api_remount_spec.rb +2 -0
  165. data/spec/grape/api_spec.rb +99 -11
  166. data/spec/grape/config_spec.rb +2 -0
  167. data/spec/grape/dsl/callbacks_spec.rb +2 -0
  168. data/spec/grape/dsl/configuration_spec.rb +2 -0
  169. data/spec/grape/dsl/desc_spec.rb +2 -0
  170. data/spec/grape/dsl/headers_spec.rb +2 -0
  171. data/spec/grape/dsl/helpers_spec.rb +4 -2
  172. data/spec/grape/dsl/inside_route_spec.rb +177 -33
  173. data/spec/grape/dsl/logger_spec.rb +2 -0
  174. data/spec/grape/dsl/middleware_spec.rb +2 -0
  175. data/spec/grape/dsl/parameters_spec.rb +2 -0
  176. data/spec/grape/dsl/request_response_spec.rb +2 -0
  177. data/spec/grape/dsl/routing_spec.rb +2 -0
  178. data/spec/grape/dsl/settings_spec.rb +2 -0
  179. data/spec/grape/dsl/validations_spec.rb +2 -0
  180. data/spec/grape/endpoint_spec.rb +21 -6
  181. data/spec/grape/entity_spec.rb +2 -0
  182. data/spec/grape/exceptions/base_spec.rb +3 -1
  183. data/spec/grape/exceptions/body_parse_errors_spec.rb +2 -0
  184. data/spec/grape/exceptions/invalid_accept_header_spec.rb +2 -0
  185. data/spec/grape/exceptions/invalid_formatter_spec.rb +2 -0
  186. data/spec/grape/exceptions/invalid_response_spec.rb +2 -0
  187. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -0
  188. data/spec/grape/exceptions/missing_mime_type_spec.rb +2 -0
  189. data/spec/grape/exceptions/missing_option_spec.rb +2 -0
  190. data/spec/grape/exceptions/unknown_options_spec.rb +2 -0
  191. data/spec/grape/exceptions/unknown_validator_spec.rb +2 -0
  192. data/spec/grape/exceptions/validation_errors_spec.rb +4 -2
  193. data/spec/grape/exceptions/validation_spec.rb +3 -1
  194. data/spec/grape/extensions/param_builders/hash_spec.rb +2 -0
  195. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +2 -0
  196. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +2 -0
  197. data/spec/grape/integration/global_namespace_function_spec.rb +2 -0
  198. data/spec/grape/integration/rack_sendfile_spec.rb +14 -8
  199. data/spec/grape/integration/rack_spec.rb +3 -1
  200. data/spec/grape/loading_spec.rb +2 -0
  201. data/spec/grape/middleware/auth/base_spec.rb +2 -0
  202. data/spec/grape/middleware/auth/dsl_spec.rb +2 -0
  203. data/spec/grape/middleware/auth/strategies_spec.rb +3 -1
  204. data/spec/grape/middleware/base_spec.rb +2 -0
  205. data/spec/grape/middleware/error_spec.rb +2 -0
  206. data/spec/grape/middleware/exception_spec.rb +3 -1
  207. data/spec/grape/middleware/formatter_spec.rb +19 -12
  208. data/spec/grape/middleware/globals_spec.rb +2 -0
  209. data/spec/grape/middleware/stack_spec.rb +11 -0
  210. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -1
  211. data/spec/grape/middleware/versioner/header_spec.rb +3 -1
  212. data/spec/grape/middleware/versioner/param_spec.rb +3 -1
  213. data/spec/grape/middleware/versioner/path_spec.rb +3 -1
  214. data/spec/grape/middleware/versioner_spec.rb +2 -0
  215. data/spec/grape/named_api_spec.rb +2 -0
  216. data/spec/grape/parser_spec.rb +7 -5
  217. data/spec/grape/path_spec.rb +6 -4
  218. data/spec/grape/presenters/presenter_spec.rb +2 -0
  219. data/spec/grape/request_spec.rb +2 -0
  220. data/spec/grape/util/inheritable_setting_spec.rb +2 -0
  221. data/spec/grape/util/inheritable_values_spec.rb +2 -0
  222. data/spec/grape/util/reverse_stackable_values_spec.rb +2 -0
  223. data/spec/grape/util/stackable_values_spec.rb +3 -1
  224. data/spec/grape/util/strict_hash_configuration_spec.rb +2 -0
  225. data/spec/grape/validations/attributes_iterator_spec.rb +2 -0
  226. data/spec/grape/validations/instance_behaivour_spec.rb +5 -3
  227. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +2 -0
  228. data/spec/grape/validations/params_scope_spec.rb +3 -1
  229. data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -4
  230. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  231. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  232. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  233. data/spec/grape/validations/types_spec.rb +9 -36
  234. data/spec/grape/validations/validators/all_or_none_spec.rb +2 -0
  235. data/spec/grape/validations/validators/allow_blank_spec.rb +2 -0
  236. data/spec/grape/validations/validators/at_least_one_of_spec.rb +2 -0
  237. data/spec/grape/validations/validators/coerce_spec.rb +341 -136
  238. data/spec/grape/validations/validators/default_spec.rb +123 -0
  239. data/spec/grape/validations/validators/exactly_one_of_spec.rb +14 -12
  240. data/spec/grape/validations/validators/except_values_spec.rb +3 -1
  241. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +2 -0
  242. data/spec/grape/validations/validators/presence_spec.rb +30 -0
  243. data/spec/grape/validations/validators/regexp_spec.rb +2 -0
  244. data/spec/grape/validations/validators/same_as_spec.rb +2 -0
  245. data/spec/grape/validations/validators/values_spec.rb +30 -5
  246. data/spec/grape/validations_spec.rb +91 -33
  247. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  248. data/spec/integration/multi_json/json_spec.rb +2 -0
  249. data/spec/integration/multi_xml/xml_spec.rb +2 -0
  250. data/spec/shared/versioning_examples.rb +2 -0
  251. data/spec/spec_helper.rb +18 -0
  252. data/spec/support/basic_auth_encode_helpers.rb +2 -0
  253. data/spec/support/content_type_helpers.rb +2 -0
  254. data/spec/support/eager_load.rb +19 -0
  255. data/spec/support/endpoint_faker.rb +2 -0
  256. data/spec/support/file_streamer.rb +2 -0
  257. data/spec/support/integer_helpers.rb +2 -0
  258. data/spec/support/versioned_helpers.rb +4 -2
  259. metadata +48 -28
  260. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
  261. data/lib/grape/util/content_types.rb +0 -26
  262. data/lib/grape/validations/types/virtus_collection_patch.rb +0 -16
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
4
  require 'grape/version'
3
5
 
@@ -11,17 +13,24 @@ Gem::Specification.new do |s|
11
13
  s.summary = 'A simple Ruby framework for building REST-like APIs.'
12
14
  s.description = 'A Ruby framework for rapid API development with great conventions.'
13
15
  s.license = 'MIT'
16
+ s.metadata = {
17
+ 'bug_tracker_uri' => 'https://github.com/ruby-grape/grape/issues',
18
+ 'changelog_uri' => "https://github.com/ruby-grape/grape/blob/v#{s.version}/CHANGELOG.md",
19
+ 'documentation_uri' => "https://www.rubydoc.info/gems/grape/#{s.version}",
20
+ 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}"
21
+ }
14
22
 
15
23
  s.add_runtime_dependency 'activesupport'
16
24
  s.add_runtime_dependency 'builder'
25
+ s.add_runtime_dependency 'dry-types', '>= 1.1'
17
26
  s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0'
18
27
  s.add_runtime_dependency 'rack', '>= 1.3.0'
19
28
  s.add_runtime_dependency 'rack-accept'
20
- s.add_runtime_dependency 'virtus', '>= 1.0.0'
21
29
 
22
30
  s.files = %w[CHANGELOG.md CONTRIBUTING.md README.md grape.png UPGRADING.md LICENSE]
23
31
  s.files += %w[grape.gemspec]
24
32
  s.files += Dir['lib/**/*']
25
33
  s.test_files = Dir['spec/**/*']
26
34
  s.require_paths = ['lib']
35
+ s.required_ruby_version = '>= 2.4.0'
27
36
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
  require 'rack'
3
5
  require 'rack/builder'
@@ -18,9 +20,6 @@ require 'active_support/core_ext/hash/conversions'
18
20
  require 'active_support/dependencies/autoload'
19
21
  require 'active_support/notifications'
20
22
  require 'i18n'
21
- require 'thread'
22
-
23
- require 'virtus'
24
23
 
25
24
  I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
26
25
 
@@ -84,7 +83,6 @@ module Grape
84
83
  eager_autoload do
85
84
  autoload :DeepMergeableHash
86
85
  autoload :DeepSymbolizeHash
87
- autoload :DeepHashWithIndifferentAccess
88
86
  autoload :Hash
89
87
  end
90
88
  module ActiveSupport
@@ -208,18 +206,19 @@ module Grape
208
206
  end
209
207
  end
210
208
 
211
- module ServeFile
209
+ module ServeStream
212
210
  extend ::ActiveSupport::Autoload
213
211
  eager_autoload do
214
- autoload :FileResponse
215
212
  autoload :FileBody
216
213
  autoload :SendfileResponse
214
+ autoload :StreamResponse
217
215
  end
218
216
  end
219
217
  end
220
218
 
221
219
  require 'grape/config'
222
- require 'grape/util/content_types'
220
+ require 'grape/content_types'
221
+
223
222
  require 'grape/util/lazy_value'
224
223
  require 'grape/util/lazy_block'
225
224
  require 'grape/util/endpoint_configuration'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'grape/router'
2
4
  require 'grape/api/instance'
3
5
 
@@ -6,7 +8,7 @@ module Grape
6
8
  # should subclass this class in order to build an API.
7
9
  class API
8
10
  # Class methods that we want to call on the API rather than on the API object
9
- NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze
11
+ NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze
10
12
 
11
13
  class << self
12
14
  attr_accessor :base_instance, :instances
@@ -173,7 +175,7 @@ module Grape
173
175
  if argument.respond_to?(:lazy?) && argument.lazy?
174
176
  argument.evaluate_from(configuration)
175
177
  elsif argument.is_a?(Hash)
176
- argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h
178
+ argument.transform_values { |value| evaluate_arguments(configuration, value).first }
177
179
  elsif argument.is_a?(Array)
178
180
  evaluate_arguments(configuration, *argument)
179
181
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  class API
3
5
  module Helpers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'grape/router'
2
4
 
3
5
  module Grape
@@ -72,7 +74,7 @@ module Grape
72
74
  # (see #cascade?)
73
75
  def cascade(value = nil)
74
76
  if value.nil?
75
- inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
77
+ inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
76
78
  else
77
79
  namespace_inheritable(:cascade, value)
78
80
  end
@@ -119,7 +121,7 @@ module Grape
119
121
  self.configuration = value_for_configuration
120
122
  response
121
123
  end
122
- if base_instance? && lazy
124
+ if base && base_instance? && lazy
123
125
  lazy_block
124
126
  else
125
127
  lazy_block.evaluate_from(configuration)
@@ -176,7 +178,7 @@ module Grape
176
178
  # errors from reaching upstream. This is effectivelly done by unsetting
177
179
  # X-Cascade. Default :cascade is true.
178
180
  def cascade?
179
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
181
+ return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
180
182
  return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
181
183
  true
182
184
  end
@@ -190,62 +192,63 @@ module Grape
190
192
  # will return an HTTP 405 response for any HTTP method that the resource
191
193
  # cannot handle.
192
194
  def add_head_not_allowed_methods_and_options_methods
193
- routes_map = {}
194
-
195
- self.class.endpoints.each do |endpoint|
196
- routes = endpoint.routes
197
- routes.each do |route|
198
- # using the :any shorthand produces [nil] for route methods, substitute all manually
199
- route_key = route.pattern.to_regexp
200
- routes_map[route_key] ||= {}
201
- route_settings = routes_map[route_key]
202
- route_settings[:pattern] = route.pattern
203
- route_settings[:requirements] = route.requirements
204
- route_settings[:path] = route.origin
205
- route_settings[:methods] ||= []
206
- route_settings[:methods] << route.request_method
207
- route_settings[:endpoint] = route.app
208
-
209
- # using the :any shorthand produces [nil] for route methods, substitute all manually
210
- route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
211
- end
212
- end
213
-
195
+ versioned_route_configs = collect_route_config_per_pattern
214
196
  # The paths we collected are prepared (cf. Path#prepare), so they
215
197
  # contain already versioning information when using path versioning.
216
198
  # Disable versioning so adding a route won't prepend versioning
217
199
  # informations again.
218
200
  without_root_prefix do
219
201
  without_versioning do
220
- routes_map.each_value do |config|
221
- methods = config[:methods]
222
- allowed_methods = methods.dup
202
+ versioned_route_configs.each do |config|
203
+ allowed_methods = config[:methods].dup
223
204
 
224
205
  unless self.class.namespace_inheritable(:do_not_route_head)
225
206
  allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
226
207
  end
227
208
 
228
- allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
209
+ allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
229
210
 
230
211
  unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
231
212
  config[:endpoint].options[:options_route_enabled] = true
232
213
  end
233
214
 
234
215
  attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
235
- generate_not_allowed_method(config[:pattern], attributes)
216
+ generate_not_allowed_method(config[:pattern], **attributes)
236
217
  end
237
218
  end
238
219
  end
239
220
  end
240
221
 
222
+ def collect_route_config_per_pattern
223
+ all_routes = self.class.endpoints.map(&:routes).flatten
224
+ routes_by_regexp = all_routes.group_by { |route| route.pattern.to_regexp }
225
+
226
+ # Build the configuration based on the first endpoint and the collection of methods supported.
227
+ routes_by_regexp.values.map do |routes|
228
+ last_route = routes.last # Most of the configuration is taken from the last endpoint
229
+ matching_wildchar = routes.any? { |route| route.request_method == '*' }
230
+ {
231
+ options: {},
232
+ pattern: last_route.pattern,
233
+ requirements: last_route.requirements,
234
+ path: last_route.origin,
235
+ endpoint: last_route.app,
236
+ methods: matching_wildchar ? Grape::Http::Headers::SUPPORTED_METHODS : routes.map(&:request_method)
237
+ }
238
+ end
239
+ end
240
+
241
241
  # Generate a route that returns an HTTP 405 response for a user defined
242
242
  # path on methods not specified
243
243
  def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
244
- not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
245
- not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
246
-
244
+ supported_methods =
245
+ if self.class.namespace_inheritable(:do_not_route_options)
246
+ Grape::Http::Headers::SUPPORTED_METHODS
247
+ else
248
+ Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
249
+ end
250
+ not_allowed_methods = supported_methods - allowed_methods
247
251
  return if not_allowed_methods.empty?
248
-
249
252
  @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
250
253
  end
251
254
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Config
3
5
  class Configuration
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grape/util/registrable'
4
+
5
+ module Grape
6
+ module ContentTypes
7
+ extend Util::Registrable
8
+
9
+ # Content types are listed in order of preference.
10
+ CONTENT_TYPES = {
11
+ xml: 'application/xml',
12
+ serializable_hash: 'application/json',
13
+ json: 'application/json',
14
+ binary: 'application/octet-stream',
15
+ txt: 'text/plain'
16
+ }.freeze
17
+
18
+ class << self
19
+ def content_types_for_settings(settings)
20
+ return if settings.blank?
21
+
22
+ settings.each_with_object({}) { |value, result| result.merge!(value) }
23
+ end
24
+
25
+ def content_types_for(from_settings)
26
+ if from_settings.present?
27
+ from_settings
28
+ else
29
+ Grape::ContentTypes::CONTENT_TYPES.merge(default_elements)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  class Cookies
3
5
  def initialize
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
 
3
5
  module Grape
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
 
3
5
  module Grape
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
 
3
5
  module Grape
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module DSL
3
5
  module Desc
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module DSL
3
5
  module Headers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
 
3
5
  module Grape
@@ -65,7 +67,7 @@ module Grape
65
67
 
66
68
  def define_boolean_in_mod(mod)
67
69
  return if defined? mod::Boolean
68
- mod.const_set('Boolean', Virtus::Attribute::Boolean)
70
+ mod.const_set('Boolean', Grape::API::Boolean)
69
71
  end
70
72
 
71
73
  def inject_api_helpers_to_mod(mod, &_block)
@@ -92,7 +94,7 @@ module Grape
92
94
  protected
93
95
 
94
96
  def process_named_params
95
- return unless @named_params && @named_params.any?
97
+ return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
96
98
  api.namespace_stackable(:named_params, @named_params)
97
99
  end
98
100
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
  require 'grape/dsl/headers'
3
5
 
@@ -26,36 +28,38 @@ module Grape
26
28
  # Methods which should not be available in filters until the before filter
27
29
  # has completed
28
30
  module PostBeforeFilter
29
- def declared(passed_params, options = {}, declared_params = nil)
31
+ def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
30
32
  options = options.reverse_merge(include_missing: true, include_parent_namespaces: true)
31
- declared_params ||= optioned_declared_params(options)
33
+ declared_params ||= optioned_declared_params(**options)
32
34
 
33
35
  if passed_params.is_a?(Array)
34
- declared_array(passed_params, options, declared_params)
36
+ declared_array(passed_params, options, declared_params, params_nested_path)
35
37
  else
36
- declared_hash(passed_params, options, declared_params)
38
+ declared_hash(passed_params, options, declared_params, params_nested_path)
37
39
  end
38
40
  end
39
41
 
40
42
  private
41
43
 
42
- def declared_array(passed_params, options, declared_params)
44
+ def declared_array(passed_params, options, declared_params, params_nested_path)
43
45
  passed_params.map do |passed_param|
44
- declared(passed_param || {}, options, declared_params)
46
+ declared(passed_param || {}, options, declared_params, params_nested_path)
45
47
  end
46
48
  end
47
49
 
48
- def declared_hash(passed_params, options, declared_params)
50
+ def declared_hash(passed_params, options, declared_params, params_nested_path)
49
51
  declared_params.each_with_object(passed_params.class.new) do |declared_param, memo|
50
52
  if declared_param.is_a?(Hash)
51
53
  declared_param.each_pair do |declared_parent_param, declared_children_params|
54
+ params_nested_path_dup = params_nested_path.dup
55
+ params_nested_path_dup << declared_parent_param.to_s
52
56
  next unless options[:include_missing] || passed_params.key?(declared_parent_param)
53
57
 
54
58
  passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
55
59
  memo_key = optioned_param_key(declared_parent_param, options)
56
60
 
57
- memo[memo_key] = handle_passed_param(declared_parent_param, passed_children_params) do
58
- declared(passed_children_params, options, declared_children_params)
61
+ memo[memo_key] = handle_passed_param(passed_children_params, params_nested_path_dup) do
62
+ declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
59
63
  end
60
64
  end
61
65
  else
@@ -75,16 +79,32 @@ module Grape
75
79
  end
76
80
  end
77
81
 
78
- def handle_passed_param(declared_param, passed_children_params, &_block)
79
- should_be_empty_array?(declared_param, passed_children_params) ? [] : yield
82
+ def handle_passed_param(passed_children_params, params_nested_path, &_block)
83
+ if should_be_empty_hash?(passed_children_params, params_nested_path)
84
+ {}
85
+ elsif should_be_empty_array?(passed_children_params, params_nested_path)
86
+ []
87
+ else
88
+ yield
89
+ end
90
+ end
91
+
92
+ def should_be_empty_array?(passed_children_params, params_nested_path)
93
+ passed_children_params.empty? && declared_param_is_array?(params_nested_path)
80
94
  end
81
95
 
82
- def should_be_empty_array?(declared_param, passed_children_params)
83
- declared_param_is_array?(declared_param) && passed_children_params.empty?
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'
84
99
  end
85
100
 
86
- def declared_param_is_array?(declared_param)
87
- route_options_params[declared_param.to_s] && route_options_params[declared_param.to_s][:type] == 'Array'
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'
88
108
  end
89
109
 
90
110
  def route_options_params
@@ -95,13 +115,19 @@ module Grape
95
115
  options[:stringify] ? declared_param.to_s : declared_param.to_sym
96
116
  end
97
117
 
98
- def optioned_declared_params(options)
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
+ def optioned_declared_params(**options)
99
125
  declared_params = if options[:include_parent_namespaces]
100
126
  # Declared params including parent namespaces
101
- route_setting(:saved_declared_params).flatten | Array(route_setting(:declared_params))
127
+ route_setting(:declared_params)
102
128
  else
103
129
  # Declared params at current namespace
104
- route_setting(:saved_declared_params).last & Array(route_setting(:declared_params))
130
+ namespace_stackable(:declared_params).last || []
105
131
  end
106
132
 
107
133
  raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
@@ -174,17 +200,17 @@ module Grape
174
200
  def status(status = nil)
175
201
  case status
176
202
  when Symbol
177
- raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.include?(status)
203
+ raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
178
204
  @status = Rack::Utils.status_code(status)
179
205
  when Integer
180
206
  @status = status
181
207
  when nil
182
- return @status if @status
208
+ return @status if instance_variable_defined?(:@status) && @status
183
209
  case request.request_method.to_s.upcase
184
210
  when Grape::Http::Headers::POST
185
211
  201
186
212
  when Grape::Http::Headers::DELETE
187
- if @body.present?
213
+ if instance_variable_defined?(:@body) && @body.present?
188
214
  200
189
215
  else
190
216
  204
@@ -235,7 +261,7 @@ module Grape
235
261
  @body = ''
236
262
  status 204
237
263
  else
238
- @body
264
+ instance_variable_defined?(:@body) ? @body : nil
239
265
  end
240
266
  end
241
267
 
@@ -253,23 +279,36 @@ module Grape
253
279
  body false
254
280
  end
255
281
 
256
- # Allows you to define the response as a file-like object.
282
+ # Deprecated method to send files to the client. Use `sendfile` or `stream`
283
+ def file(value = nil)
284
+ if value.is_a?(String)
285
+ warn '[DEPRECATION] Use sendfile or stream to send files.'
286
+ sendfile(value)
287
+ elsif !value.is_a?(NilClass)
288
+ warn '[DEPRECATION] Use stream to use a Stream object.'
289
+ stream(value)
290
+ else
291
+ warn '[DEPRECATION] Use sendfile or stream to send files.'
292
+ sendfile
293
+ end
294
+ end
295
+
296
+ # Allows you to send a file to the client via sendfile.
257
297
  #
258
298
  # @example
259
299
  # get '/file' do
260
- # file FileStreamer.new(...)
300
+ # sendfile FileStreamer.new(...)
261
301
  # end
262
302
  #
263
303
  # GET /file # => "contents of file"
264
- def file(value = nil)
304
+ def sendfile(value = nil)
265
305
  if value.is_a?(String)
266
- file_body = Grape::ServeFile::FileBody.new(value)
267
- @file = Grape::ServeFile::FileResponse.new(file_body)
306
+ file_body = Grape::ServeStream::FileBody.new(value)
307
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
268
308
  elsif !value.is_a?(NilClass)
269
- warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.'
270
- @file = Grape::ServeFile::FileResponse.new(value)
309
+ raise ArgumentError, 'Argument must be a file path'
271
310
  else
272
- @file
311
+ stream
273
312
  end
274
313
  end
275
314
 
@@ -292,7 +331,16 @@ module Grape
292
331
  header 'Content-Length', nil
293
332
  header 'Transfer-Encoding', nil
294
333
  header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
295
- file(value)
334
+ if value.is_a?(String)
335
+ file_body = Grape::ServeStream::FileBody.new(value)
336
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
337
+ elsif value.respond_to?(:each)
338
+ @stream = Grape::ServeStream::StreamResponse.new(value)
339
+ elsif !value.is_a?(NilClass)
340
+ raise ArgumentError, 'Stream object must respond to :each.'
341
+ else
342
+ instance_variable_defined?(:@stream) ? @stream : nil
343
+ end
296
344
  end
297
345
 
298
346
  # Allows you to make use of Grape Entities by setting
@@ -328,11 +376,12 @@ module Grape
328
376
  end
329
377
 
330
378
  representation = { root => representation } if root
379
+
331
380
  if key
332
- representation = (@body || {}).merge(key => representation)
333
- elsif entity_class.present? && @body
381
+ representation = (body || {}).merge(key => representation)
382
+ elsif entity_class.present? && body
334
383
  raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
335
- representation = @body.merge(representation)
384
+ representation = body.merge(representation)
336
385
  end
337
386
 
338
387
  body representation