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
@@ -6,136 +6,94 @@ module Grape
6
6
  # on the instance level of this class may be called
7
7
  # from inside a `get`, `post`, etc.
8
8
  class Endpoint
9
+ extend Forwardable
9
10
  include Grape::DSL::Settings
11
+ include Grape::DSL::Headers
10
12
  include Grape::DSL::InsideRoute
11
13
 
12
- attr_accessor :block, :source, :options
13
- attr_reader :env, :request, :headers, :params
14
+ attr_reader :env, :request, :source, :options, :endpoints
15
+ attr_accessor :options_route_enabled
14
16
 
15
- class << self
16
- def new(*args, &block)
17
- self == Endpoint ? Class.new(Endpoint).new(*args, &block) : super
18
- end
17
+ def_delegators :request, :params, :headers, :cookies
18
+ def_delegator :cookies, :response_cookies
19
19
 
20
- def before_each(new_setup = false, &block)
21
- @before_each ||= []
22
- if new_setup == false
23
- return @before_each unless block
20
+ # The API (a +Grape::API+ instance) this endpoint belongs to.
21
+ def_delegator :@config, :api
24
22
 
25
- @before_each << block
26
- else
27
- @before_each = [new_setup]
28
- end
29
- end
23
+ # The logger configured on the API this endpoint belongs to. Available
24
+ # inside route handlers, +before+/+after+/+after_validation+/+finally+
25
+ # filters, and +rescue_from+ blocks.
26
+ def_delegator :api, :logger
30
27
 
31
- def run_before_each(endpoint)
32
- superclass.run_before_each(endpoint) unless self == Endpoint
33
- before_each.each { |blk| blk.call(endpoint) if blk.respond_to?(:call) }
34
- end
28
+ # The Rack app or Grape API mounted at this endpoint, or +nil+ for a plain
29
+ # block endpoint. Prefer this over +options[:app]+, which is retained only
30
+ # for backwards compatibility.
31
+ def mounted_app
32
+ config.app
33
+ end
35
34
 
36
- # @api private
37
- #
38
- # Create an UnboundMethod that is appropriate for executing an endpoint
39
- # route.
40
- #
41
- # The unbound method allows explicit calls to +return+ without raising a
42
- # +LocalJumpError+. The method will be removed, but a +Proc+ reference to
43
- # it will be returned. The returned +Proc+ expects a single argument: the
44
- # instance of +Endpoint+ to bind to the method during the call.
45
- #
46
- # @param [String, Symbol] method_name
47
- # @return [Proc]
48
- # @raise [NameError] an instance method with the same name already exists
49
- def generate_api_method(method_name, &block)
50
- raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") if method_defined?(method_name)
51
-
52
- define_method(method_name, &block)
53
- method = instance_method(method_name)
54
- remove_method(method_name)
55
-
56
- proc do |endpoint_instance|
57
- ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
58
- method.bind(endpoint_instance).call
59
- end
60
- end
35
+ class << self
36
+ def block_to_unbound_method(block)
37
+ return unless block
38
+
39
+ define_method :temp_unbound_method, block
40
+ method = instance_method(:temp_unbound_method)
41
+ remove_method :temp_unbound_method
42
+ method
61
43
  end
62
44
  end
63
45
 
64
46
  # Create a new endpoint.
65
47
  # @param new_settings [InheritableSetting] settings to determine the params,
66
48
  # validations, and other properties from.
67
- # @param options [Hash] attributes of this endpoint
68
- # @option options path [String or Array] the path to this endpoint, within
69
- # the current scope.
70
- # @option options method [String or Array] which HTTP method(s) can be used
71
- # to reach this endpoint.
49
+ # @param http_methods [String or Array] which HTTP method(s) can be used to
50
+ # reach this endpoint.
51
+ # @param path [String or Array] the path to this endpoint, within the
52
+ # current scope.
53
+ # @param api [Grape::API] the API this endpoint belongs to. Exposed as
54
+ # {#api}.
55
+ # @param app [#call, nil] the Rack app or Grape API mounted at this
56
+ # endpoint; +nil+ for a plain block endpoint. Exposed as {#mounted_app}.
57
+ # @param params [Hash] the declared params for this endpoint, keyed by name.
58
+ # Kept out of +route_options+ and read via +config.params+.
59
+ # @param requirements [Hash, nil] regular-expression constraints for named
60
+ # path params. Read via +config.requirements+.
61
+ # @param anchor [Boolean] whether the route anchors to the whole path
62
+ # (default +true+). Read via +config.anchor+.
63
+ # @param options [Hash] attributes of this endpoint, normalized into a
64
+ # +Grape::Endpoint::Options+ value object.
72
65
  # @option options route_options [Hash]
73
66
  # @note This happens at the time of API definition, so in this context the
74
67
  # endpoint does not know if it will be mounted under a different endpoint.
75
68
  # @yield a block defining what your API should do when this endpoint is hit
76
- def initialize(new_settings, options = {}, &block)
77
- require_option(options, :path)
78
- require_option(options, :method)
79
-
80
- self.inheritable_setting = new_settings.point_in_time_copy
81
-
82
- # now +namespace_stackable(:declared_params)+ contains all params defined for
83
- # this endpoint and its parents, but later it will be cleaned up,
84
- # see +reset_validations!+ in lib/grape/dsl/validations.rb
85
- route_setting(:declared_params, namespace_stackable(:declared_params).flatten)
86
- route_setting(:saved_validations, namespace_stackable(:validations))
87
-
88
- namespace_stackable(:representations, []) unless namespace_stackable(:representations)
89
- namespace_inheritable(:default_error_status, 500) unless namespace_inheritable(:default_error_status)
69
+ def initialize(new_settings, http_methods:, path:, api:, app: nil, params: {}, requirements: nil, anchor: true, **options, &block)
70
+ self.inheritable_setting = new_settings.point_in_time_copy_for_endpoint
90
71
 
91
72
  @options = options
92
-
93
- @options[:path] = Array(options[:path])
94
- @options[:path] << '/' if options[:path].empty?
95
-
96
- @options[:method] = Array(options[:method])
97
- @options[:route_options] ||= {}
98
-
99
- @lazy_initialize_lock = Mutex.new
100
- @lazy_initialized = nil
101
- @block = nil
73
+ @config = Options.new(http_methods:, path:, api:, app:, params:, requirements:, anchor:, **options)
74
+ # +:app+ is still surfaced on the public options Hash for backwards
75
+ # compatibility (e.g. grape-swagger); prefer the +mounted_app+ reader.
76
+ @options[:app] = app if app
102
77
 
103
78
  @status = nil
104
79
  @stream = nil
105
80
  @body = nil
106
- @proc = nil
107
-
108
- return unless block
109
-
110
- @source = block
111
- @block = self.class.generate_api_method(method_name, &block)
81
+ @source = self.class.block_to_unbound_method(block)
82
+ @before_filter_passed = false
83
+ @options_route_enabled = false
84
+ @endpoints = @config.app.endpoints if @config.app.respond_to?(:endpoints)
112
85
  end
113
86
 
114
- # Update our settings from a given set of stackable parameters. Used when
87
+ # Update our settings from a given parent settings instance. Used when
115
88
  # the endpoint's API is mounted under another one.
116
- def inherit_settings(namespace_stackable)
117
- inheritable_setting.route[:saved_validations] += namespace_stackable[:validations]
118
- parent_declared_params = namespace_stackable[:declared_params]
119
-
120
- inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params
121
-
122
- endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
123
- end
124
-
125
- def require_option(options, key)
126
- raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
127
- end
128
-
129
- def method_name
130
- [options[:method],
131
- Namespace.joined_space(namespace_stackable(:namespace)),
132
- (namespace_stackable(:mount_path) || []).join('/'),
133
- options[:path].join('/')]
134
- .join(' ')
89
+ # @param settings [Grape::Util::InheritableSetting]
90
+ def inherit_settings(settings)
91
+ inheritable_setting.inherit_route_params(settings)
92
+ endpoints&.each { |e| e.inherit_settings(settings) }
135
93
  end
136
94
 
137
95
  def routes
138
- @routes ||= endpoints ? endpoints.collect(&:routes).flatten : to_routes
96
+ @routes ||= endpoints&.flat_map(&:routes) || to_routes
139
97
  end
140
98
 
141
99
  def reset_routes!
@@ -146,76 +104,26 @@ module Grape
146
104
 
147
105
  def mount_in(router)
148
106
  if endpoints
149
- endpoints.each { |e| e.mount_in(router) }
150
- else
151
- reset_routes!
152
- routes.each do |route|
153
- methods = [route.request_method]
154
- methods << Grape::Http::Headers::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
155
- methods.each do |method|
156
- route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) unless route.request_method == method
157
- router.append(route.apply(self))
158
- end
159
- end
107
+ compile!
108
+ return endpoints.each { |e| e.mount_in(router) }
160
109
  end
161
- end
162
-
163
- def to_routes
164
- route_options = prepare_default_route_attributes
165
- map_routes do |method, path|
166
- path = prepare_path(path)
167
- params = merge_route_options(**route_options.merge(suffix: path.suffix))
168
- route = Router::Route.new(method, path.path, **params)
169
- route.apply(self)
170
- end.flatten
171
- end
172
-
173
- def prepare_routes_requirements
174
- {}.merge!(*namespace_stackable(:namespace).map(&:requirements)).tap do |requirements|
175
- endpoint_requirements = options.dig(:route_options, :requirements)
176
- requirements.merge!(endpoint_requirements) if endpoint_requirements
177
- end
178
- end
179
-
180
- def prepare_default_route_attributes
181
- {
182
- namespace: namespace,
183
- version: prepare_version,
184
- requirements: prepare_routes_requirements,
185
- prefix: namespace_inheritable(:root_prefix),
186
- anchor: options[:route_options].fetch(:anchor, true),
187
- settings: inheritable_setting.route.except(:declared_params, :saved_validations),
188
- forward_match: options[:forward_match]
189
- }
190
- end
191
-
192
- def prepare_version
193
- version = namespace_inheritable(:version) || []
194
- return if version.empty?
195
110
 
196
- version.length == 1 ? version.first.to_s : version
197
- end
198
-
199
- def merge_route_options(**default)
200
- options[:route_options].clone.merge!(**default)
201
- end
111
+ reset_routes!
112
+ compile!
113
+ routes.each do |route|
114
+ router.append(route.apply(self))
115
+ next if inheritable_setting.do_not_route_head? || route.request_method != Rack::GET
202
116
 
203
- def map_routes
204
- options[:method].map { |method| options[:path].map { |path| yield method, path } }
205
- end
206
-
207
- def prepare_path(path)
208
- path_settings = inheritable_setting.to_hash[:namespace_stackable].merge(inheritable_setting.to_hash[:namespace_inheritable])
209
- Path.prepare(path, namespace, path_settings)
117
+ router.append(route.to_head.apply(self))
118
+ end
210
119
  end
211
120
 
212
121
  def namespace
213
- @namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace))
122
+ @namespace ||= inheritable_setting.namespace_path
214
123
  end
215
124
 
216
125
  def call(env)
217
- lazy_initialize!
218
- dup.call!(env)
126
+ @prototype.dup.call!(env)
219
127
  end
220
128
 
221
129
  def call!(env)
@@ -224,44 +132,54 @@ module Grape
224
132
  @app.call(env)
225
133
  end
226
134
 
227
- # Return the collection of endpoints within this endpoint.
228
- # This is the case when an Grape::API mounts another Grape::API.
229
- def endpoints
230
- options[:app].endpoints if options[:app].respond_to?(:endpoints)
135
+ def ==(other)
136
+ other.is_a?(self.class) &&
137
+ config == other.config &&
138
+ inheritable_setting == other.inheritable_setting
231
139
  end
140
+ alias eql? ==
232
141
 
233
- def equals?(e)
234
- (options == e.options) && (inheritable_setting.to_hash == e.inheritable_setting.to_hash)
142
+ # Mirrors #==. The class stays out: #== admits a subclass instance
143
+ # through is_a?, and such a pair must hash alike. The block (#source) is
144
+ # not part of either, matching the long-standing duplicate-route check in
145
+ # DSL::Routing#route.
146
+ def hash
147
+ [config, inheritable_setting].hash
148
+ end
149
+
150
+ # The purpose of this override is solely for stripping internals when an error occurs while calling
151
+ # an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
152
+ # Otherwise, it calls super.
153
+ def inspect
154
+ return super unless env
155
+
156
+ "#{self.class} in '#{route.origin}' endpoint"
235
157
  end
236
158
 
237
159
  protected
238
160
 
239
161
  def run
240
- ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
241
- @header = {}
242
- @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
243
- @params = @request.params
244
- @headers = @request.headers
162
+ instrument_run do
163
+ @request = Grape::Request.new(env, build_params_with: @build_params_with)
245
164
  begin
246
- cookies.read(@request)
247
- self.class.run_before_each(self)
248
165
  run_filters befores, :before
166
+ @before_filter_passed = true
249
167
 
250
- if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
251
- raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options?
168
+ if env.key?(Grape::Env::GRAPE_ALLOWED_METHODS)
169
+ header['Allow'] = env[Grape::Env::GRAPE_ALLOWED_METHODS].join(', ')
170
+ raise Grape::Exceptions::MethodNotAllowed.new(header) unless options?
252
171
 
253
- header 'Allow', allowed_methods
254
172
  response_object = ''
255
173
  status 204
256
174
  else
257
175
  run_filters before_validations, :before_validation
258
- run_validators validations, request
176
+ run_validators(request:)
259
177
  run_filters after_validations, :after_validation
260
178
  response_object = execute
261
179
  end
262
180
 
263
181
  run_filters afters, :after
264
- cookies.write(header)
182
+ build_response_cookies
265
183
 
266
184
  # status verifies body presence when DELETE
267
185
  @body ||= response_object
@@ -276,132 +194,264 @@ module Grape
276
194
  end
277
195
  end
278
196
 
279
- def build_stack(helpers)
280
- stack = Grape::Middleware::Stack.new
197
+ def execute
198
+ return unless source
281
199
 
282
- stack.use Rack::Head
283
- stack.use Class.new(Grape::Middleware::Error),
284
- helpers: helpers,
285
- format: namespace_inheritable(:format),
286
- content_types: namespace_stackable_with_hash(:content_types),
287
- default_status: namespace_inheritable(:default_error_status),
288
- rescue_all: namespace_inheritable(:rescue_all),
289
- rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
290
- default_error_formatter: namespace_inheritable(:default_error_formatter),
291
- error_formatters: namespace_stackable_with_hash(:error_formatters),
292
- rescue_options: namespace_stackable_with_hash(:rescue_options) || {},
293
- rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers) || {},
294
- base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers) || {},
295
- all_rescue_handler: namespace_inheritable(:all_rescue_handler),
296
- grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
297
-
298
- stack.concat namespace_stackable(:middleware)
299
-
300
- if namespace_inheritable(:version)
301
- stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
302
- versions: namespace_inheritable(:version)&.flatten,
303
- version_options: namespace_inheritable(:version_options),
304
- prefix: namespace_inheritable(:root_prefix),
305
- mount_path: namespace_stackable(:mount_path).first
200
+ instrument_render do
201
+ source.bind_call(self)
306
202
  end
203
+ end
307
204
 
308
- stack.use Grape::Middleware::Formatter,
309
- format: namespace_inheritable(:format),
310
- default_format: namespace_inheritable(:default_format) || :txt,
311
- content_types: namespace_stackable_with_hash(:content_types),
312
- formatters: namespace_stackable_with_hash(:formatters),
313
- parsers: namespace_stackable_with_hash(:parsers)
205
+ def run_validators(request:)
206
+ validators = inheritable_setting.route_validations
207
+ return if validators.blank?
314
208
 
315
- builder = stack.build
316
- builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
317
- builder.to_app
209
+ validation_exceptions = nil
210
+
211
+ Grape::Validations::ParamScopeTracker.track do
212
+ instrument_run_validators(validators, request) do
213
+ validators.each do |validator|
214
+ validator.validate(request)
215
+ rescue Grape::Exceptions::Validation, Grape::Exceptions::ValidationArrayErrors => e
216
+ (validation_exceptions ||= []) << e
217
+ break if validator.fail_fast?
218
+ end
219
+ end
220
+ end
221
+
222
+ raise Grape::Exceptions::ValidationErrors.new(exceptions: validation_exceptions, headers: header) if validation_exceptions
318
223
  end
319
224
 
320
- def build_helpers
321
- helpers = namespace_stackable(:helpers)
322
- Module.new { helpers&.each { |mod_to_include| include mod_to_include } }
225
+ def run_filters(filters, type = :other)
226
+ return if filters.blank?
227
+
228
+ instrument_run_filters(filters, type) do
229
+ filters.each { |filter| instance_eval(&filter) }
230
+ end
323
231
  end
324
232
 
325
- private :build_stack, :build_helpers
233
+ attr_reader :befores, :before_validations, :after_validations, :afters, :finallies, :config
326
234
 
327
- def execute
328
- @block&.call(self)
235
+ def options?
236
+ options_route_enabled && env[Rack::REQUEST_METHOD] == Rack::OPTIONS
237
+ end
238
+
239
+ private
240
+
241
+ attr_reader :before_filter_passed
242
+
243
+ # Instrument helpers. Each guards on +listening?+ so that with no subscriber
244
+ # the payload Hash and notification machinery are skipped and the block runs
245
+ # directly (no added allocations); the block is forwarded anonymously so
246
+ # nothing is allocated unless a subscriber is present.
247
+ def instrument_run(&)
248
+ return yield unless ActiveSupport::Notifications.notifier.listening?('endpoint_run.grape')
249
+
250
+ ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env:, &)
251
+ end
252
+
253
+ def instrument_render(&)
254
+ return yield unless ActiveSupport::Notifications.notifier.listening?('endpoint_render.grape')
255
+
256
+ ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: self, &)
257
+ end
258
+
259
+ def instrument_run_validators(validators, request, &)
260
+ return yield unless ActiveSupport::Notifications.notifier.listening?('endpoint_run_validators.grape')
261
+
262
+ ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators:, request:, &)
263
+ end
264
+
265
+ def instrument_run_filters(filters, type, &)
266
+ return yield unless ActiveSupport::Notifications.notifier.listening?('endpoint_run_filters.grape')
267
+
268
+ ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters:, type:, &)
269
+ end
270
+
271
+ def compile!
272
+ @app = config.app || build_stack
273
+ warn_unauthenticated_mounted_app
274
+ @helpers = build_helpers
275
+ callbacks = inheritable_setting.callbacks
276
+ @befores = callbacks.fetch(:before)
277
+ @before_validations = callbacks.fetch(:before_validation)
278
+ @after_validations = callbacks.fetch(:after_validation)
279
+ @afters = callbacks.fetch(:after)
280
+ @finallies = callbacks.fetch(:finally)
281
+ @build_params_with = inheritable_setting.build_params_with
282
+ @prototype = build_prototype
283
+ end
284
+
285
+ # The object each request is copied from.
286
+ #
287
+ # Helpers reach an endpoint by module inclusion, and +Object#dup+ does not
288
+ # carry a singleton class over — so including them on the per-request copy
289
+ # meant giving every request a brand-new singleton class, whose method
290
+ # cache then started cold for the helpers, the route block and every DSL
291
+ # method the endpoint answers. Include them once into a subclass owned by
292
+ # this endpoint instead, and copy this endpoint's state onto an instance of
293
+ # it: the per-request copy already answers to the helpers, against a class
294
+ # that has been warm since boot. It also keeps request handling free of
295
+ # class mutation.
296
+ #
297
+ # Endpoints with no helpers are their own prototype, exactly as before.
298
+ #
299
+ # +allocate+ rather than +new+ because the constructor rebuilds state from
300
+ # the DSL's arguments — a fresh settings copy, a fresh +@config+, another
301
+ # +block_to_unbound_method+ — and would still not hold what +compile!+ has
302
+ # just computed. What is wanted is what +dup+ itself does (allocate, then
303
+ # copy the ivars over) across a class boundary, which neither +dup+ nor
304
+ # +initialize_copy+ will do: both insist on the receiver's own class. So
305
+ # the copy is written out here. Taking all of them is safe because
306
+ # everything set past this point (+routes+, +namespace+) is route-building
307
+ # memoization the request path never reads.
308
+ def build_prototype
309
+ return self unless @helpers
310
+
311
+ klass = Class.new(self.class)
312
+ # Anonymous classes answer nil to +name+ and render as a bare address,
313
+ # which would reach anything that reports on the endpoint — +inspect+
314
+ # here, a logger or an error tracker elsewhere. The annotation is what
315
+ # +set_temporary_name+ is for; a plain constant path is rejected, which
316
+ # is right, since this class is not reachable under that name.
317
+ klass.set_temporary_name("#{self.class}(helpers)")
318
+ klass.include(@helpers)
319
+ prototype = klass.allocate
320
+ # Everything but the prototype slot itself, so that recompiling (a
321
+ # remount) does not leave the new prototype holding the one it replaces.
322
+ (instance_variables - [:@prototype]).each { |name| prototype.instance_variable_set(name, instance_variable_get(name)) }
323
+ prototype
329
324
  end
330
325
 
331
- def helpers
332
- lazy_initialize! && @helpers
326
+ def to_routes
327
+ route_options = config.route_options
328
+ params = config.params
329
+ path_settings = inheritable_setting.path_settings
330
+ forward_match = bare_rack_app?
331
+ version = prepare_version(inheritable_setting.version)
332
+ prefix = inheritable_setting.root_prefix
333
+ requirements = prepare_routes_requirements(config.requirements)
334
+ anchor = config.anchor
335
+ settings = inheritable_setting.route_settings
336
+
337
+ config.http_methods.flat_map do |method|
338
+ config.path.map do |path|
339
+ pattern = Grape::Router::Pattern.build(
340
+ path:,
341
+ namespace:,
342
+ settings: path_settings,
343
+ anchor:,
344
+ params:,
345
+ version:,
346
+ requirements:
347
+ )
348
+ Grape::Router::Route.new(self, method, pattern, route_options, forward_match:, params:, namespace:, prefix:, settings:)
349
+ end
350
+ end
333
351
  end
334
352
 
335
- def lazy_initialize!
336
- return true if @lazy_initialized
353
+ # True when a bare Rack app (anything that isn't a Grape app) is mounted at
354
+ # this endpoint. Such an app is called directly and matched by path prefix
355
+ # rather than an anchored route.
356
+ def bare_rack_app?
357
+ config.app && !config.app.is_a?(Grape::Mountable)
358
+ end
337
359
 
338
- @lazy_initialize_lock.synchronize do
339
- return true if @lazy_initialized
360
+ def prepare_routes_requirements(route_options_requirements)
361
+ namespace_requirements = inheritable_setting.namespace_requirements
362
+ namespace_requirements << route_options_requirements if route_options_requirements.present?
363
+ namespace_requirements.reduce({}, :merge)
364
+ end
340
365
 
341
- @helpers = build_helpers.tap { |mod| self.class.send(:include, mod) }
342
- @app = options[:app] || build_stack(@helpers)
366
+ def prepare_version(namespace_inheritable_version)
367
+ return if namespace_inheritable_version.blank?
343
368
 
344
- @lazy_initialized = true
345
- end
369
+ namespace_inheritable_version.length == 1 ? namespace_inheritable_version.first : namespace_inheritable_version
346
370
  end
347
371
 
348
- def run_validators(validators, request)
349
- validation_errors = []
372
+ def build_stack
373
+ stack = Grape::Middleware::Stack.new
350
374
 
351
- ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
352
- validators.each do |validator|
353
- validator.validate(request)
354
- rescue Grape::Exceptions::Validation => e
355
- validation_errors << e
356
- break if validator.fail_fast?
357
- rescue Grape::Exceptions::ValidationArrayErrors => e
358
- validation_errors.concat e.errors
359
- break if validator.fail_fast?
360
- end
375
+ content_types = inheritable_setting.content_types
376
+ format = inheritable_setting.format
377
+
378
+ stack.use Rack::Head
379
+ stack.use Rack::Lint if lint?
380
+ stack.use Grape::Middleware::Error, **error_middleware_options(format, content_types)
381
+
382
+ stack.concat inheritable_setting.middleware
383
+
384
+ if inheritable_setting.version.present?
385
+ version_options = inheritable_setting.version_options
386
+ stack.use Grape::Middleware::Versioner.using(version_options.using),
387
+ versions: inheritable_setting.version.flatten,
388
+ version_options:,
389
+ prefix: inheritable_setting.root_prefix,
390
+ mount_path: inheritable_setting.mount_path
361
391
  end
362
392
 
363
- validation_errors.any? && raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header))
364
- end
393
+ stack.use Grape::Middleware::Formatter,
394
+ format:,
395
+ default_format: inheritable_setting.default_format || :txt,
396
+ content_types:,
397
+ formatters: inheritable_setting.formatters,
398
+ parsers: inheritable_setting.parsers
365
399
 
366
- def run_filters(filters, type = :other)
367
- ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
368
- filters&.each { |filter| instance_eval(&filter) }
369
- end
370
- post_extension = DSL::InsideRoute.post_filter_methods(type)
371
- extend post_extension if post_extension
400
+ builder = stack.build
401
+ builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
402
+ builder.to_app
372
403
  end
373
404
 
374
- def befores
375
- namespace_stackable(:befores)
405
+ def error_middleware_options(format, content_types)
406
+ setting = inheritable_setting
407
+ {
408
+ format:,
409
+ content_types:,
410
+ default_status: setting.default_error_status,
411
+ rescue_all: setting.rescue_all?,
412
+ rescue_grape_exceptions: setting.rescue_grape_exceptions?,
413
+ default_error_formatter: setting.default_error_formatter,
414
+ error_formatters: setting.error_formatters,
415
+ rescue_options: setting.rescue_options,
416
+ rescue_handlers: setting.rescue_handlers,
417
+ base_only_rescue_handlers: setting.base_only_rescue_handlers,
418
+ all_rescue_handler: setting.all_rescue_handler,
419
+ grape_exceptions_rescue_handler: setting.grape_exceptions_rescue_handler,
420
+ internal_grape_exceptions_rescue_handler: setting.internal_grape_exceptions_rescue_handler
421
+ }
376
422
  end
377
423
 
378
- def before_validations
379
- namespace_stackable(:before_validations)
380
- end
424
+ def build_helpers
425
+ helpers = inheritable_setting.helpers
426
+ return if helpers.empty?
381
427
 
382
- def after_validations
383
- namespace_stackable(:after_validations)
428
+ Module.new { helpers.each { |mod_to_include| include mod_to_include } }
384
429
  end
385
430
 
386
- def afters
387
- namespace_stackable(:afters)
388
- end
431
+ # A bare Rack app mounted with +mount+ is called directly (see +compile!+):
432
+ # it does not go through +build_stack+, so the API's authentication
433
+ # middleware never runs and the mount is reachable unauthenticated. Mounted
434
+ # Grape APIs are unaffected because they rebuild their own stack from the
435
+ # inherited settings. Warn so this bypass isn't silent.
436
+ def warn_unauthenticated_mounted_app
437
+ return unless bare_rack_app?
438
+ return unless inheritable_setting.auth
389
439
 
390
- def finallies
391
- namespace_stackable(:finallies)
440
+ warn "Grape: #{config.app} is mounted under an API that declares authentication, but authentication " \
441
+ 'middleware does not wrap mounted Rack applications. Requests to this mount are not authenticated by Grape.'
392
442
  end
393
443
 
394
- def validations
395
- return enum_for(:validations) unless block_given?
444
+ def build_response_cookies
445
+ return unless request.cookies?
396
446
 
397
- route_setting(:saved_validations)&.each do |saved_validation|
398
- yield Grape::Validations::ValidatorFactory.create_validator(**saved_validation)
447
+ response_cookies do |name, value|
448
+ cookie_value = value.is_a?(Hash) ? value : { value: }
449
+ Rack::Utils.set_cookie_header! header, name, cookie_value
399
450
  end
400
451
  end
401
452
 
402
- def options?
403
- options[:options_route_enabled] &&
404
- env[Grape::Http::Headers::REQUEST_METHOD] == Grape::Http::Headers::OPTIONS
453
+ def lint?
454
+ inheritable_setting.lint? || Grape.config.lint
405
455
  end
406
456
  end
407
457
  end