grape 1.8.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +517 -1
  3. data/CONTRIBUTING.md +19 -11
  4. data/README.md +841 -545
  5. data/UPGRADING.md +1553 -7
  6. data/grape.gemspec +11 -14
  7. data/lib/grape/api/instance.rb +77 -160
  8. data/lib/grape/api.rb +75 -107
  9. data/lib/grape/content_types.rb +67 -9
  10. data/lib/grape/cookies.rb +28 -25
  11. data/lib/grape/declared_params_handler.rb +116 -0
  12. data/lib/grape/dry_types.rb +48 -6
  13. data/lib/grape/dsl/callbacks.rb +8 -58
  14. data/lib/grape/dsl/declared.rb +35 -0
  15. data/lib/grape/dsl/desc.rb +25 -63
  16. data/lib/grape/dsl/entity.rb +106 -0
  17. data/lib/grape/dsl/headers.rb +2 -2
  18. data/lib/grape/dsl/helpers.rb +83 -64
  19. data/lib/grape/dsl/inside_route.rb +69 -296
  20. data/lib/grape/dsl/logger.rb +4 -9
  21. data/lib/grape/dsl/middleware.rb +22 -40
  22. data/lib/grape/dsl/parameters.rb +88 -94
  23. data/lib/grape/dsl/request_response.rb +143 -147
  24. data/lib/grape/dsl/rescue_options.rb +25 -0
  25. data/lib/grape/dsl/routing.rb +333 -191
  26. data/lib/grape/dsl/settings.rb +29 -135
  27. data/lib/grape/dsl/validations.rb +39 -32
  28. data/lib/grape/dsl/version_options.rb +23 -0
  29. data/lib/grape/endpoint/options.rb +25 -0
  30. data/lib/grape/endpoint.rb +319 -269
  31. data/lib/grape/{util/env.rb → env.rb} +8 -8
  32. data/lib/grape/error_formatter/base.rb +57 -21
  33. data/lib/grape/error_formatter/json.rb +13 -15
  34. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  35. data/lib/grape/error_formatter/txt.rb +12 -18
  36. data/lib/grape/error_formatter/xml.rb +3 -13
  37. data/lib/grape/error_formatter.rb +9 -25
  38. data/lib/grape/exceptions/base.rb +22 -58
  39. data/lib/grape/exceptions/error_response.rb +48 -0
  40. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  41. data/lib/grape/exceptions/internal_server_error.rb +16 -0
  42. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  43. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  44. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  45. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  46. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  47. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  48. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  49. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  50. data/lib/grape/exceptions/request_error.rb +11 -0
  51. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  52. data/lib/grape/exceptions/unknown_error_formatter.rb +11 -0
  53. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  54. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  55. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  56. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  57. data/lib/grape/exceptions/validation.rb +28 -11
  58. data/lib/grape/exceptions/validation_array_errors.rb +5 -0
  59. data/lib/grape/exceptions/validation_errors.rb +22 -26
  60. data/lib/grape/formatter/base.rb +16 -0
  61. data/lib/grape/formatter/json.rb +5 -6
  62. data/lib/grape/formatter/serializable_hash.rb +7 -10
  63. data/lib/grape/formatter/txt.rb +3 -5
  64. data/lib/grape/formatter/xml.rb +4 -6
  65. data/lib/grape/formatter.rb +7 -25
  66. data/lib/grape/json.rb +46 -0
  67. data/lib/grape/locale/en.yml +44 -42
  68. data/lib/grape/middleware/auth/base.rb +11 -33
  69. data/lib/grape/middleware/auth/dsl.rb +12 -34
  70. data/lib/grape/middleware/auth/strategies.rb +1 -2
  71. data/lib/grape/middleware/base.rb +56 -33
  72. data/lib/grape/middleware/error.rb +290 -91
  73. data/lib/grape/middleware/formatter.rb +164 -106
  74. data/lib/grape/middleware/precomputed_content_types.rb +51 -0
  75. data/lib/grape/middleware/stack.rb +38 -40
  76. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  77. data/lib/grape/middleware/versioner/base.rb +66 -0
  78. data/lib/grape/middleware/versioner/header.rb +44 -129
  79. data/lib/grape/middleware/versioner/param.rb +4 -25
  80. data/lib/grape/middleware/versioner/path.rb +48 -25
  81. data/lib/grape/middleware/versioner.rb +7 -14
  82. data/lib/grape/mountable.rb +22 -0
  83. data/lib/grape/namespace.rb +21 -14
  84. data/lib/grape/params_builder/base.rb +20 -0
  85. data/lib/grape/params_builder/hash.rb +11 -0
  86. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  87. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  88. data/lib/grape/params_builder.rb +15 -0
  89. data/lib/grape/parser/base.rb +16 -0
  90. data/lib/grape/parser/json.rb +6 -8
  91. data/lib/grape/parser/xml.rb +6 -8
  92. data/lib/grape/parser.rb +5 -23
  93. data/lib/grape/path.rb +8 -94
  94. data/lib/grape/precompiled_json.rb +50 -0
  95. data/lib/grape/railtie.rb +9 -0
  96. data/lib/grape/request.rb +200 -26
  97. data/lib/grape/router/base_route.rb +85 -0
  98. data/lib/grape/router/greedy_route.rb +30 -0
  99. data/lib/grape/router/mustermann_pattern.rb +44 -0
  100. data/lib/grape/router/pattern/path.rb +78 -0
  101. data/lib/grape/router/pattern.rb +77 -35
  102. data/lib/grape/router/route.rb +79 -60
  103. data/lib/grape/router.rb +128 -104
  104. data/lib/grape/serve_stream/file_body.rb +7 -0
  105. data/lib/grape/serve_stream/sendfile_response.rb +3 -5
  106. data/lib/grape/serve_stream/stream_response.rb +7 -0
  107. data/lib/grape/testing.rb +33 -0
  108. data/lib/grape/util/api_description.rb +67 -0
  109. data/lib/grape/util/cache.rb +22 -5
  110. data/lib/grape/util/deep_freeze.rb +34 -0
  111. data/lib/grape/util/endpoint_configuration.rb +1 -1
  112. data/lib/grape/util/freeze_on_new.rb +20 -0
  113. data/lib/grape/util/header.rb +13 -0
  114. data/lib/grape/util/inheritable_setting.rb +777 -39
  115. data/lib/grape/util/lazy/base.rb +16 -0
  116. data/lib/grape/util/lazy/block.rb +22 -0
  117. data/lib/grape/util/lazy/value.rb +31 -0
  118. data/lib/grape/util/lazy/value_array.rb +21 -0
  119. data/lib/grape/util/lazy/value_enumerable.rb +31 -0
  120. data/lib/grape/util/lazy/value_hash.rb +21 -0
  121. data/lib/grape/util/media_type.rb +74 -0
  122. data/lib/grape/util/path_normalizer.rb +37 -0
  123. data/lib/grape/util/registry.rb +37 -0
  124. data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
  125. data/lib/grape/util/stackable_values.rb +40 -16
  126. data/lib/grape/util/translation.rb +42 -0
  127. data/lib/grape/validations/attributes_iterator.rb +61 -28
  128. data/lib/grape/validations/coerce_options.rb +21 -0
  129. data/lib/grape/validations/contract_scope.rb +29 -0
  130. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  131. data/lib/grape/validations/oneof_collector.rb +35 -0
  132. data/lib/grape/validations/param_scope_tracker.rb +62 -0
  133. data/lib/grape/validations/params_documentation.rb +52 -0
  134. data/lib/grape/validations/params_scope.rb +223 -306
  135. data/lib/grape/validations/shared_options.rb +19 -0
  136. data/lib/grape/validations/single_attribute_iterator.rb +6 -4
  137. data/lib/grape/validations/types/array_coercer.rb +7 -12
  138. data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
  139. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  140. data/lib/grape/validations/types/dry_type_coercer.rb +17 -28
  141. data/lib/grape/validations/types/json.rb +1 -5
  142. data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
  143. data/lib/grape/validations/types/primitive_coercer.rb +14 -35
  144. data/lib/grape/validations/types/set_coercer.rb +1 -4
  145. data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
  146. data/lib/grape/validations/types.rb +29 -54
  147. data/lib/grape/validations/validations_spec.rb +164 -0
  148. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  149. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  150. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  151. data/lib/grape/validations/validators/base.rb +118 -37
  152. data/lib/grape/validations/validators/coerce_validator.rb +26 -38
  153. data/lib/grape/validations/validators/contract_scope_validator.rb +46 -0
  154. data/lib/grape/validations/validators/default_validator.rb +13 -16
  155. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  156. data/lib/grape/validations/validators/except_values_validator.rb +15 -5
  157. data/lib/grape/validations/validators/length_validator.rb +50 -0
  158. data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
  159. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  160. data/lib/grape/validations/validators/oneof_validator.rb +51 -0
  161. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  162. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  163. data/lib/grape/validations/validators/same_as_validator.rb +7 -15
  164. data/lib/grape/validations/validators/values_validator.rb +36 -65
  165. data/lib/grape/validations.rb +8 -21
  166. data/lib/grape/version.rb +1 -2
  167. data/lib/grape/xml.rb +17 -0
  168. data/lib/grape.rb +96 -288
  169. metadata +83 -294
  170. data/lib/grape/api/helpers.rb +0 -9
  171. data/lib/grape/dsl/api.rb +0 -19
  172. data/lib/grape/dsl/configuration.rb +0 -15
  173. data/lib/grape/eager_load.rb +0 -20
  174. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  175. data/lib/grape/exceptions/missing_option.rb +0 -11
  176. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  177. data/lib/grape/exceptions/unknown_options.rb +0 -11
  178. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
  179. data/lib/grape/extensions/hash.rb +0 -22
  180. data/lib/grape/extensions/hashie/mash.rb +0 -26
  181. data/lib/grape/http/headers.rb +0 -61
  182. data/lib/grape/middleware/globals.rb +0 -16
  183. data/lib/grape/middleware/helpers.rb +0 -12
  184. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  185. data/lib/grape/router/attribute_translator.rb +0 -63
  186. data/lib/grape/types/invalid_value.rb +0 -8
  187. data/lib/grape/util/base_inheritable.rb +0 -43
  188. data/lib/grape/util/inheritable_values.rb +0 -31
  189. data/lib/grape/util/json.rb +0 -12
  190. data/lib/grape/util/lazy_block.rb +0 -27
  191. data/lib/grape/util/lazy_object.rb +0 -43
  192. data/lib/grape/util/lazy_value.rb +0 -91
  193. data/lib/grape/util/registrable.rb +0 -15
  194. data/lib/grape/util/reverse_stackable_values.rb +0 -20
  195. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  196. data/lib/grape/util/xml.rb +0 -10
  197. data/lib/grape/validations/attributes_doc.rb +0 -58
  198. data/lib/grape/validations/types/build_coercer.rb +0 -94
  199. data/lib/grape/validations/validator_factory.rb +0 -15
  200. data/spec/grape/api/custom_validations_spec.rb +0 -213
  201. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  202. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  203. data/spec/grape/api/documentation_spec.rb +0 -59
  204. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  205. data/spec/grape/api/instance_spec.rb +0 -103
  206. data/spec/grape/api/invalid_format_spec.rb +0 -45
  207. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  208. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  209. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  210. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  211. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  212. data/spec/grape/api/recognize_path_spec.rb +0 -21
  213. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  214. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  215. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  216. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  217. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  218. data/spec/grape/api_remount_spec.rb +0 -509
  219. data/spec/grape/api_spec.rb +0 -4356
  220. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  221. data/spec/grape/dsl/desc_spec.rb +0 -98
  222. data/spec/grape/dsl/headers_spec.rb +0 -62
  223. data/spec/grape/dsl/helpers_spec.rb +0 -100
  224. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  225. data/spec/grape/dsl/logger_spec.rb +0 -24
  226. data/spec/grape/dsl/middleware_spec.rb +0 -60
  227. data/spec/grape/dsl/parameters_spec.rb +0 -180
  228. data/spec/grape/dsl/request_response_spec.rb +0 -225
  229. data/spec/grape/dsl/routing_spec.rb +0 -275
  230. data/spec/grape/dsl/settings_spec.rb +0 -261
  231. data/spec/grape/dsl/validations_spec.rb +0 -55
  232. data/spec/grape/endpoint/declared_spec.rb +0 -846
  233. data/spec/grape/endpoint_spec.rb +0 -1085
  234. data/spec/grape/entity_spec.rb +0 -336
  235. data/spec/grape/exceptions/base_spec.rb +0 -81
  236. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  237. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  238. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  239. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  240. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  241. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  242. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  243. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  244. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  245. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  246. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  247. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  248. data/spec/grape/exceptions/validation_spec.rb +0 -19
  249. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  250. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  251. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  252. data/spec/grape/grape_spec.rb +0 -9
  253. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  254. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  255. data/spec/grape/integration/rack_spec.rb +0 -51
  256. data/spec/grape/loading_spec.rb +0 -44
  257. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  258. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  259. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  260. data/spec/grape/middleware/base_spec.rb +0 -221
  261. data/spec/grape/middleware/error_spec.rb +0 -85
  262. data/spec/grape/middleware/exception_spec.rb +0 -294
  263. data/spec/grape/middleware/formatter_spec.rb +0 -461
  264. data/spec/grape/middleware/globals_spec.rb +0 -30
  265. data/spec/grape/middleware/stack_spec.rb +0 -155
  266. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  267. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  268. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  269. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  270. data/spec/grape/middleware/versioner_spec.rb +0 -21
  271. data/spec/grape/named_api_spec.rb +0 -19
  272. data/spec/grape/parser_spec.rb +0 -86
  273. data/spec/grape/path_spec.rb +0 -252
  274. data/spec/grape/presenters/presenter_spec.rb +0 -71
  275. data/spec/grape/request_spec.rb +0 -126
  276. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  277. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  278. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  279. data/spec/grape/util/stackable_values_spec.rb +0 -128
  280. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  281. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  282. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  283. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  284. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  285. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  286. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  287. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  288. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  289. data/spec/grape/validations/types_spec.rb +0 -111
  290. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  291. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  292. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  293. data/spec/grape/validations/validators/base_spec.rb +0 -38
  294. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  295. data/spec/grape/validations/validators/default_spec.rb +0 -463
  296. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  297. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  298. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  299. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  300. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  301. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  302. data/spec/grape/validations/validators/values_spec.rb +0 -733
  303. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  304. data/spec/grape/validations_spec.rb +0 -2030
  305. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  306. data/spec/integration/multi_json/json_spec.rb +0 -7
  307. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  308. data/spec/shared/deprecated_class_examples.rb +0 -16
  309. data/spec/shared/versioning_examples.rb +0 -215
  310. data/spec/spec_helper.rb +0 -52
  311. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  312. data/spec/support/chunks.rb +0 -14
  313. data/spec/support/content_type_helpers.rb +0 -15
  314. data/spec/support/endpoint_faker.rb +0 -25
  315. data/spec/support/file_streamer.rb +0 -13
  316. data/spec/support/integer_helpers.rb +0 -13
  317. data/spec/support/versioned_helpers.rb +0 -55
@@ -10,9 +10,9 @@ module Grape
10
10
  # 4. Delete a specifc header key-value pair
11
11
  def header(key = nil, val = nil)
12
12
  if key
13
- val ? header[key.to_s] = val : header.delete(key.to_s)
13
+ val ? header[key] = val : header.delete(key)
14
14
  else
15
- @header ||= {}
15
+ @header ||= Grape::Util::Header.new
16
16
  end
17
17
  end
18
18
  alias headers header
@@ -3,76 +3,95 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Helpers
6
- extend ActiveSupport::Concern
7
- include Grape::DSL::Configuration
8
-
9
- module ClassMethods
10
- # Add helper methods that will be accessible from any
11
- # endpoint within this namespace (and child namespaces).
12
- #
13
- # When called without a block, all known helpers within this scope
14
- # are included.
15
- #
16
- # @param [Array] new_modules optional array of modules to include
17
- # @param [Block] block optional block of methods to include
18
- #
19
- # @example Define some helpers.
20
- #
21
- # class ExampleAPI < Grape::API
22
- # helpers do
23
- # def current_user
24
- # User.find_by_id(params[:token])
25
- # end
26
- # end
27
- # end
28
- #
29
- # @example Include many modules
30
- #
31
- # class ExampleAPI < Grape::API
32
- # helpers Authentication, Mailer, OtherModule
33
- # end
34
- #
35
- def helpers(*new_modules, &block)
36
- include_new_modules(new_modules) if new_modules.any?
37
- include_block(block) if block
38
- include_all_in_scope if !block && new_modules.empty?
39
- end
6
+ # Add helper methods that will be accessible from any
7
+ # endpoint within this namespace (and child namespaces).
8
+ #
9
+ # When called without a block, all known helpers within this scope
10
+ # are included.
11
+ #
12
+ # @param [Array] new_modules optional array of modules to include
13
+ # @param [Block] block optional block of methods to include
14
+ #
15
+ # @example Define some helpers.
16
+ #
17
+ # class ExampleAPI < Grape::API
18
+ # helpers do
19
+ # def current_user
20
+ # User.find_by_id(params[:token])
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # @example Include many modules
26
+ #
27
+ # class ExampleAPI < Grape::API
28
+ # helpers Authentication, Mailer, OtherModule
29
+ # end
30
+ #
31
+ def helpers(*new_modules, &block)
32
+ include_new_modules(new_modules)
33
+ include_block(block)
34
+ include_all_in_scope if !block && new_modules.empty?
35
+ end
40
36
 
41
- protected
37
+ private
42
38
 
43
- def include_new_modules(modules)
44
- modules.each { |mod| make_inclusion(mod) }
45
- end
39
+ def include_new_modules(modules)
40
+ return if modules.empty?
46
41
 
47
- def include_block(block)
48
- Module.new.tap do |mod|
49
- make_inclusion(mod) { mod.class_eval(&block) }
50
- end
51
- end
42
+ modules.each { |mod| make_inclusion(mod) }
43
+ end
52
44
 
53
- def make_inclusion(mod, &block)
54
- define_boolean_in_mod(mod)
55
- inject_api_helpers_to_mod(mod, &block)
56
- namespace_stackable(:helpers, mod)
57
- end
45
+ def include_block(block)
46
+ return unless block
58
47
 
59
- def include_all_in_scope
60
- Module.new.tap do |mod|
61
- namespace_stackable(:helpers).each { |mod_to_include| mod.send :include, mod_to_include }
62
- change!
63
- end
64
- end
48
+ mod = Module.new
49
+ make_inclusion(mod) { mod.class_eval(&block) }
50
+ end
65
51
 
66
- def define_boolean_in_mod(mod)
67
- return if defined? mod::Boolean
52
+ def make_inclusion(mod, &)
53
+ define_boolean_in_mod(mod)
54
+ inject_api_helpers_to_mod(mod, &)
55
+ inheritable_setting.add_helper(mod)
56
+ end
68
57
 
69
- mod.const_set(:Boolean, Grape::API::Boolean)
70
- end
58
+ def include_all_in_scope
59
+ mod = Module.new
60
+ inheritable_setting.helpers.each { |mod_to_include| mod.include mod_to_include }
61
+ change!
62
+ mod
63
+ end
64
+
65
+ def define_boolean_in_mod(mod)
66
+ return if defined? mod::Boolean
67
+
68
+ mod.const_set(:Boolean, Grape::API::Boolean)
69
+ end
70
+
71
+ def inject_api_helpers_to_mod(mod, &block)
72
+ mod.extend(BaseHelper) unless mod.is_a?(BaseHelper)
73
+ yield if block
74
+ warn_on_endpoint_overrides(mod) if Grape.config.warn_on_helper_overrides
75
+ mod.api_changed(self)
76
+ end
71
77
 
72
- def inject_api_helpers_to_mod(mod, &block)
73
- mod.extend(BaseHelper) unless mod.is_a?(BaseHelper)
74
- yield if block
75
- mod.api_changed(self)
78
+ # When +Grape.config.warn_on_helper_overrides+ is enabled, emit a
79
+ # warning to +$stderr+ for any helper method that masks an instance
80
+ # method on +Grape::Endpoint+. Helpers are mixed into the endpoint's
81
+ # singleton class and therefore take precedence over +Endpoint+
82
+ # instance methods — usually intentional, but a common source of
83
+ # surprise when the framework gains a method that already collides
84
+ # with an existing helper name.
85
+ def warn_on_endpoint_overrides(mod)
86
+ overridden = mod.instance_methods(false).select { |m| Grape::Endpoint.method_defined?(m) }
87
+ return if overridden.empty?
88
+
89
+ overridden.each do |name|
90
+ warn(
91
+ "Grape: helper method `#{name}` overrides Grape::Endpoint##{name}. " \
92
+ 'The helper takes precedence. To use the framework implementation, remove the helper. ' \
93
+ 'Silence this warning by setting Grape.config.warn_on_helper_overrides = false.'
94
+ )
76
95
  end
77
96
  end
78
97
 
@@ -94,9 +113,9 @@ module Grape
94
113
  protected
95
114
 
96
115
  def process_named_params
97
- return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
116
+ return if @named_params.blank?
98
117
 
99
- api.namespace_stackable(:named_params, @named_params)
118
+ api.inheritable_setting.add_named_params(@named_params)
100
119
  end
101
120
  end
102
121
  end
@@ -1,150 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/dsl/headers'
4
-
5
3
  module Grape
6
4
  module DSL
7
5
  module InsideRoute
8
- extend ActiveSupport::Concern
9
- include Grape::DSL::Settings
10
- include Grape::DSL::Headers
11
-
12
- # Denotes a situation where a DSL method has been invoked in a
13
- # filter which it should not yet be available in
14
- class MethodNotYetAvailable < StandardError; end
15
-
16
- # @param type [Symbol] The type of filter for which evaluation has been
17
- # completed
18
- # @return [Module] A module containing method overrides suitable for the
19
- # position in the filter evaluation sequence denoted by +type+. This
20
- # defaults to an empty module if no overrides are defined for the given
21
- # filter +type+.
22
- def self.post_filter_methods(type)
23
- @post_filter_modules ||= { before: PostBeforeFilter }
24
- @post_filter_modules[type]
25
- end
26
-
27
- # Methods which should not be available in filters until the before filter
28
- # has completed
29
- module PostBeforeFilter
30
- def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
31
- options = options.reverse_merge(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
32
- declared_params ||= optioned_declared_params(**options)
33
-
34
- if passed_params.is_a?(Array)
35
- declared_array(passed_params, options, declared_params, params_nested_path)
36
- else
37
- declared_hash(passed_params, options, declared_params, params_nested_path)
38
- end
39
- end
40
-
41
- private
42
-
43
- def declared_array(passed_params, options, declared_params, params_nested_path)
44
- passed_params.map do |passed_param|
45
- declared(passed_param || {}, options, declared_params, params_nested_path)
46
- end
47
- end
6
+ include Declared
7
+ include Entity
48
8
 
49
- def declared_hash(passed_params, options, declared_params, params_nested_path)
50
- declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
51
- next if options[:evaluate_given] && !declared_param_attr.scope.attr_meets_dependency?(passed_params)
52
-
53
- declared_hash_attr(passed_params, options, declared_param_attr.key, params_nested_path, memo)
54
- end
55
- end
56
-
57
- def declared_hash_attr(passed_params, options, declared_param, params_nested_path, memo)
58
- renamed_params = route_setting(:renamed_params) || {}
59
- if declared_param.is_a?(Hash)
60
- declared_param.each_pair do |declared_parent_param, declared_children_params|
61
- params_nested_path_dup = params_nested_path.dup
62
- params_nested_path_dup << declared_parent_param.to_s
63
- next unless options[:include_missing] || passed_params.key?(declared_parent_param)
64
-
65
- rename_path = params_nested_path + [declared_parent_param.to_s]
66
- renamed_param_name = renamed_params[rename_path]
67
-
68
- memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
69
- passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
70
-
71
- memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
72
- declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
73
- end
74
- end
75
- else
76
- # If it is not a Hash then it does not have children.
77
- # Find its value or set it to nil.
78
- return unless options[:include_missing] || passed_params.key?(declared_param)
79
-
80
- rename_path = params_nested_path + [declared_param.to_s]
81
- renamed_param_name = renamed_params[rename_path]
82
-
83
- memo_key = optioned_param_key(renamed_param_name || declared_param, options)
84
- passed_param = passed_params[declared_param]
85
-
86
- params_nested_path_dup = params_nested_path.dup
87
- params_nested_path_dup << declared_param.to_s
88
- memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
89
- passed_param
90
- end
91
- end
92
- end
93
-
94
- def handle_passed_param(params_nested_path, has_passed_children = false, &_block)
95
- return yield if has_passed_children
96
-
97
- key = params_nested_path[0]
98
- key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1
99
-
100
- route_options_params = options[:route_options][:params] || {}
101
- type = route_options_params.dig(key, :type)
102
- has_children = route_options_params.keys.any? { |k| k != key && k.start_with?(key) }
103
-
104
- if type == 'Hash' && !has_children
105
- {}
106
- elsif type == 'Array' || (type&.start_with?('[') && type&.exclude?(','))
107
- []
108
- elsif type == 'Set' || type&.start_with?('#<Set')
109
- Set.new
110
- else
111
- yield
112
- end
113
- end
114
-
115
- def optioned_param_key(declared_param, options)
116
- options[:stringify] ? declared_param.to_s : declared_param.to_sym
117
- end
118
-
119
- def optioned_declared_params(**options)
120
- declared_params = if options[:include_parent_namespaces]
121
- # Declared params including parent namespaces
122
- route_setting(:declared_params)
123
- else
124
- # Declared params at current namespace
125
- namespace_stackable(:declared_params).last || []
126
- end
127
-
128
- raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
129
-
130
- declared_params
131
- end
132
- end
133
-
134
- # A filtering method that will return a hash
135
- # consisting only of keys that have been declared by a
136
- # `params` statement against the current/target endpoint or parent
137
- # namespaces.
138
- #
139
- # @see +PostBeforeFilter#declared+
140
- #
141
- # @param params [Hash] The initial hash to filter. Usually this will just be `params`
142
- # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
143
- # options. `:include_parent_namespaces` defaults to true, hence must be set to false if
144
- # you want only to return params declared against the current/target endpoint.
145
- def declared(*)
146
- raise MethodNotYetAvailable, '#declared is not available prior to parameter validation.'
147
- end
9
+ # Backward compatibility: alias exception class to previous location
10
+ MethodNotYetAvailable = Declared::MethodNotYetAvailable
148
11
 
149
12
  # The API version as specified in the URL.
150
13
  def version
@@ -152,33 +15,36 @@ module Grape
152
15
  end
153
16
 
154
17
  def configuration
155
- options[:for].configuration.evaluate
18
+ config.api.configuration.evaluate
156
19
  end
157
20
 
158
21
  # End the request and display an error to the
159
22
  # end user with the specified message.
160
23
  #
161
24
  # @param message [String] The message to display.
162
- # @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
25
+ # @param status [Integer] The HTTP Status Code. Defaults to default_error_status, 500 if not set.
163
26
  # @param additional_headers [Hash] Addtional headers for the response.
164
- def error!(message, status = nil, additional_headers = nil)
165
- self.status(status || namespace_inheritable(:default_error_status))
27
+ # @param backtrace [Array<String>] The backtrace of the exception that caused the error.
28
+ # @param original_exception [Exception] The original exception that caused the error.
29
+ def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
30
+ resolved_status = self.status(status || inheritable_setting.default_error_status)
166
31
  headers = additional_headers.present? ? header.merge(additional_headers) : header
167
- throw :error, message: message, status: self.status, headers: headers
32
+ throw :error, Grape::Exceptions::ErrorResponse.new(
33
+ message:, status: resolved_status, headers:, backtrace:, original_exception:
34
+ )
168
35
  end
169
36
 
170
37
  # Redirect to a new url.
171
38
  #
172
39
  # @param url [String] The url to be redirect.
173
- # @param options [Hash] The options used when redirect.
174
- # :permanent, default false.
175
- # :body, default a short message including the URL.
176
- def redirect(url, permanent: false, body: nil, **_options)
40
+ # @param permanent [Boolean] default false.
41
+ # @param body default a short message including the URL.
42
+ def redirect(url, permanent: false, body: nil)
177
43
  body_message = body
178
44
  if permanent
179
45
  status 301
180
46
  body_message ||= "This resource has been moved permanently to #{url}."
181
- elsif env[Grape::Http::Headers::HTTP_VERSION] == 'HTTP/1.1' && request.request_method.to_s.upcase != Grape::Http::Headers::GET
47
+ elsif http_version == 'HTTP/1.1' && !request.get?
182
48
  status 303
183
49
  body_message ||= "An alternate resource is located at #{url}."
184
50
  else
@@ -187,6 +53,12 @@ module Grape
187
53
  end
188
54
  header 'Location', url
189
55
  content_type 'text/plain'
56
+ # Render the message Grape generated as the plain text it is. Setting
57
+ # only the header left it to the API's own formatter, which on a JSON
58
+ # API returned the sentence wrapped in quotes under a text/plain content
59
+ # type. A caller-supplied body keeps the API's format: it may be
60
+ # structured, and the txt formatter would render a Hash through `to_s`.
61
+ api_format :txt unless body
190
62
  body body_message
191
63
  end
192
64
 
@@ -194,52 +66,21 @@ module Grape
194
66
  #
195
67
  # @param status [Integer] The HTTP Status Code to return for this request.
196
68
  def status(status = nil)
197
- case status
198
- when Symbol
199
- raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
69
+ return @status || default_status if status.nil?
200
70
 
71
+ case status
72
+ when Symbol, Integer
201
73
  @status = Rack::Utils.status_code(status)
202
- when Integer
203
- @status = status
204
- when nil
205
- return @status if instance_variable_defined?(:@status) && @status
206
-
207
- case request.request_method.to_s.upcase
208
- when Grape::Http::Headers::POST
209
- 201
210
- when Grape::Http::Headers::DELETE
211
- if instance_variable_defined?(:@body) && @body.present?
212
- 200
213
- else
214
- 204
215
- end
216
- else
217
- 200
218
- end
219
74
  else
220
- raise ArgumentError, 'Status code must be Integer or Symbol.'
75
+ raise ArgumentError, 'status code must be Integer or Symbol'
221
76
  end
222
77
  end
223
78
 
224
79
  # Set response content-type
225
80
  def content_type(val = nil)
226
- if val
227
- header(Grape::Http::Headers::CONTENT_TYPE, val)
228
- else
229
- header[Grape::Http::Headers::CONTENT_TYPE]
230
- end
231
- end
81
+ return header(Rack::CONTENT_TYPE, val) if val
232
82
 
233
- # Set or get a cookie
234
- #
235
- # @example
236
- # cookies[:mycookie] = 'mycookie val'
237
- # cookies['mycookie-string'] = 'mycookie string val'
238
- # cookies[:more] = { value: '123', expires: Time.at(0) }
239
- # cookies.delete :more
240
- #
241
- def cookies
242
- @cookies ||= Cookies.new
83
+ header[Rack::CONTENT_TYPE]
243
84
  end
244
85
 
245
86
  # Allows you to define the response body as something other than the
@@ -259,7 +100,7 @@ module Grape
259
100
  @body = ''
260
101
  status 204
261
102
  else
262
- instance_variable_defined?(:@body) ? @body : nil
103
+ @body
263
104
  end
264
105
  end
265
106
 
@@ -273,24 +114,9 @@ module Grape
273
114
  #
274
115
  # DELETE /12 # => 204 No Content, ""
275
116
  def return_no_content
276
- status 204
277
117
  body false
278
118
  end
279
119
 
280
- # Deprecated method to send files to the client. Use `sendfile` or `stream`
281
- def file(value = nil)
282
- if value.is_a?(String)
283
- ActiveSupport::Deprecation.warn('Use sendfile or stream to send files.')
284
- sendfile(value)
285
- elsif !value.is_a?(NilClass)
286
- ActiveSupport::Deprecation.warn('Use stream to use a Stream object.')
287
- stream(value)
288
- else
289
- ActiveSupport::Deprecation.warn('Use sendfile or stream to send files.')
290
- sendfile
291
- end
292
- end
293
-
294
120
  # Allows you to send a file to the client via sendfile.
295
121
  #
296
122
  # @example
@@ -300,14 +126,12 @@ module Grape
300
126
  #
301
127
  # GET /file # => "contents of file"
302
128
  def sendfile(value = nil)
303
- if value.is_a?(String)
304
- file_body = Grape::ServeStream::FileBody.new(value)
305
- @stream = Grape::ServeStream::StreamResponse.new(file_body)
306
- elsif !value.is_a?(NilClass)
307
- raise ArgumentError, 'Argument must be a file path'
308
- else
309
- stream
310
- end
129
+ return stream if value.nil?
130
+
131
+ raise ArgumentError, 'argument must be a file path' unless value.is_a?(String)
132
+
133
+ file_body = Grape::ServeStream::FileBody.new(value)
134
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
311
135
  end
312
136
 
313
137
  # Allows you to define the response as a streamable object.
@@ -328,64 +152,13 @@ module Grape
328
152
  def stream(value = nil)
329
153
  return if value.nil? && @stream.nil?
330
154
 
331
- header 'Content-Length', nil
155
+ header Rack::CONTENT_LENGTH, nil
332
156
  header 'Transfer-Encoding', nil
333
- header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
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
- @stream
343
- end
344
- end
345
-
346
- # Allows you to make use of Grape Entities by setting
347
- # the response body to the serializable hash of the
348
- # entity provided in the `:with` option. This has the
349
- # added benefit of automatically passing along environment
350
- # and version information to the serialization, making it
351
- # very easy to do conditional exposures. See Entity docs
352
- # for more info.
353
- #
354
- # @example
355
- #
356
- # get '/users/:id' do
357
- # present User.find(params[:id]),
358
- # with: API::Entities::User,
359
- # admin: current_user.admin?
360
- # end
361
- def present(*args)
362
- options = args.count > 1 ? args.extract_options! : {}
363
- key, object = if args.count == 2 && args.first.is_a?(Symbol)
364
- args
365
- else
366
- [nil, args.first]
367
- end
368
- entity_class = entity_class_for_obj(object, options)
369
-
370
- root = options.delete(:root)
371
-
372
- representation = if entity_class
373
- entity_representation_for(entity_class, object, options)
374
- else
375
- object
376
- end
377
-
378
- representation = { root => representation } if root
157
+ header Rack::CACHE_CONTROL, 'no-cache' # Skips ETag generation (reading the response up front)
379
158
 
380
- if key
381
- representation = (body || {}).merge(key => representation)
382
- elsif entity_class.present? && body
383
- raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
159
+ return @stream if value.nil?
384
160
 
385
- representation = body.merge(representation)
386
- end
387
-
388
- body representation
161
+ @stream = Grape::ServeStream::StreamResponse.new(stream_body(value))
389
162
  end
390
163
 
391
164
  # Returns route information for the current request.
@@ -400,41 +173,41 @@ module Grape
400
173
  env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
401
174
  end
402
175
 
403
- # Attempt to locate the Entity class for a given object, if not given
404
- # explicitly. This is done by looking for the presence of Klass::Entity,
405
- # where Klass is the class of the `object` parameter, or one of its
406
- # ancestors.
407
- # @param object [Object] the object to locate the Entity class for
408
- # @param options [Hash]
409
- # @option options :with [Class] the explicit entity class to use
410
- # @return [Class] the located Entity class, or nil if none is found
411
- def entity_class_for_obj(object, options)
412
- entity_class = options.delete(:with)
176
+ def http_version
177
+ env.fetch('HTTP_VERSION') { env[Rack::SERVER_PROTOCOL] }
178
+ end
413
179
 
414
- if entity_class.nil?
415
- # entity class not explicitly defined, auto-detect from relation#klass or first object in the collection
416
- object_class = if object.respond_to?(:klass)
417
- object.klass
418
- else
419
- object.respond_to?(:first) ? object.first.class : object.class
420
- end
180
+ def api_format(format)
181
+ env[Grape::Env::API_FORMAT] = format
182
+ end
421
183
 
422
- object_class.ancestors.each do |potential|
423
- entity_class ||= (namespace_stackable_with_hash(:representations) || {})[potential]
424
- end
184
+ def context
185
+ self
186
+ end
425
187
 
426
- entity_class ||= object_class.const_get(:Entity) if object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
427
- end
188
+ private
189
+
190
+ # Wraps a stream +value+ into a body that responds to +:each+.
191
+ def stream_body(value)
192
+ return Grape::ServeStream::FileBody.new(value) if value.is_a?(String)
428
193
 
429
- entity_class
194
+ raise ArgumentError, 'stream object must respond to :each' unless value.respond_to?(:each)
195
+
196
+ value
430
197
  end
431
198
 
432
- # @return the representation of the given object as done through
433
- # the given entity_class.
434
- def entity_representation_for(entity_class, object, options)
435
- embeds = { env: env }
436
- embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
437
- entity_class.represent(object, **embeds.merge(options))
199
+ # The default HTTP status when none has been set explicitly.
200
+ # Reads the request method once instead of asking through +post?+ and
201
+ # +delete?+, each of which reads it again. Every response that did not set
202
+ # a status of its own comes through here, so it is read straight off the
203
+ # env: +Grape::Request+ wraps the very same Hash and answers
204
+ # +request_method+ with the same lookup, two method calls further down.
205
+ def default_status
206
+ request_method = env[Rack::REQUEST_METHOD]
207
+ return 201 if request_method == Rack::POST
208
+ return 204 if request_method == Rack::DELETE && @body.blank?
209
+
210
+ 200
438
211
  end
439
212
  end
440
213
  end
@@ -3,19 +3,14 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Logger
6
- include Grape::DSL::Settings
7
-
8
- attr_writer :logger
9
-
10
6
  # Set or retrive the configured logger. If none was configured, this
11
7
  # method will create a new one, logging to stdout.
12
8
  # @param logger [Object] the new logger to use
13
9
  def logger(logger = nil)
14
- if logger
15
- global_setting(:logger, logger)
16
- else
17
- global_setting(:logger) || global_setting(:logger, ::Logger.new($stdout))
18
- end
10
+ global_settings = inheritable_setting.global
11
+ return global_settings[:logger] = logger if logger
12
+
13
+ global_settings[:logger] || global_settings[:logger] = ::Logger.new($stdout)
19
14
  end
20
15
  end
21
16
  end