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
data/grape.gemspec CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- $LOAD_PATH.unshift File.expand_path('lib', __dir__)
4
- require 'grape/version'
3
+ require_relative 'lib/grape/version'
5
4
 
6
5
  Gem::Specification.new do |s|
7
6
  s.name = 'grape'
@@ -17,20 +16,18 @@ Gem::Specification.new do |s|
17
16
  'bug_tracker_uri' => 'https://github.com/ruby-grape/grape/issues',
18
17
  'changelog_uri' => "https://github.com/ruby-grape/grape/blob/v#{s.version}/CHANGELOG.md",
19
18
  'documentation_uri' => "https://www.rubydoc.info/gems/grape/#{s.version}",
20
- 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}"
19
+ 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}",
20
+ 'rubygems_mfa_required' => 'true'
21
21
  }
22
22
 
23
- s.add_runtime_dependency 'activesupport', '>= 5'
24
- s.add_runtime_dependency 'builder'
25
- s.add_runtime_dependency 'dry-types', '>= 1.1'
26
- s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0'
27
- s.add_runtime_dependency 'rack', '>= 1.3.0'
28
- s.add_runtime_dependency 'rack-accept'
23
+ s.add_dependency 'activesupport', '>= 7.2'
24
+ s.add_dependency 'dry-configurable', '>= 1.0'
25
+ s.add_dependency 'dry-types', '>= 1.1'
26
+ s.add_dependency 'mustermann', '>= 4.0'
27
+ s.add_dependency 'rack', '>= 2.2.4'
28
+ s.add_dependency 'zeitwerk', '>= 2.6'
29
29
 
30
- s.files = %w[CHANGELOG.md CONTRIBUTING.md README.md grape.png UPGRADING.md LICENSE]
31
- s.files += %w[grape.gemspec]
32
- s.files += Dir['lib/**/*']
33
- s.test_files = Dir['spec/**/*']
30
+ s.files = Dir['lib/**/*', 'CHANGELOG.md', 'CONTRIBUTING.md', 'README.md', 'grape.png', 'UPGRADING.md', 'LICENSE', 'grape.gemspec']
34
31
  s.require_paths = ['lib']
35
- s.required_ruby_version = '>= 2.6.0'
32
+ s.required_ruby_version = '>= 3.3'
36
33
  end
@@ -1,37 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/router'
4
-
5
3
  module Grape
6
4
  class API
7
5
  # The API Instance class, is the engine behind Grape::API. Each class that inherits
8
6
  # from this will represent a different API instance
9
7
  class Instance
10
- include Grape::DSL::API
8
+ # Marks this and every mounted instance as a mountable Grape app (see Grape::Mountable).
9
+ extend Grape::Mountable
10
+ extend Grape::DSL::Settings
11
+ extend Grape::DSL::Desc
12
+ extend Grape::DSL::Validations
13
+ extend Grape::DSL::Callbacks
14
+ extend Grape::DSL::Logger
15
+ extend Grape::DSL::Middleware
16
+ extend Grape::DSL::RequestResponse
17
+ extend Grape::DSL::Routing
18
+ extend Grape::DSL::Helpers
19
+ extend Grape::Middleware::Auth::DSL
20
+
21
+ Boolean = Grape::API::Boolean
11
22
 
12
23
  class << self
13
- attr_reader :instance, :base
14
24
  attr_accessor :configuration
15
25
 
16
- def given(conditional_option, &block)
17
- evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block
26
+ def to_s
27
+ @base&.to_s || super
18
28
  end
19
29
 
20
- def mounted(&block)
21
- evaluate_as_instance_with_configuration(block, lazy: true)
22
- end
30
+ attr_reader :base
23
31
 
24
32
  def base=(grape_api)
25
33
  @base = grape_api
26
34
  grape_api.instances << self
27
35
  end
28
36
 
29
- def to_s
30
- base&.to_s || super
31
- end
32
-
33
37
  def base_instance?
34
- self == base.base_instance
38
+ self == @base.base_instance
35
39
  end
36
40
 
37
41
  # A class-level lock to ensure the API is not compiled by multiple
@@ -45,91 +49,32 @@ module Grape
45
49
  reset_validations!
46
50
  end
47
51
 
48
- # Parses the API's definition and compiles it into an instance of
49
- # Grape::API.
50
- def compile
51
- @instance ||= new
52
- end
53
-
54
- # Wipe the compiled API so we can recompile after changes were made.
55
- def change!
56
- @instance = nil
57
- end
58
-
59
52
  # This is the interface point between Rack and Grape; it accepts a request
60
53
  # from Rack and ultimately returns an array of three values: the status,
61
54
  # the headers, and the body. See [the rack specification]
62
55
  # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
63
56
  def call(env)
64
- compile!
65
- call!(env)
66
- end
67
-
68
- # A non-synchronized version of ::call.
69
- def call!(env)
70
- instance.call(env)
71
- end
72
-
73
- # (see #cascade?)
74
- def cascade(value = nil)
75
- if value.nil?
76
- inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
77
- else
78
- namespace_inheritable(:cascade, value)
79
- end
57
+ compile!.call(env)
80
58
  end
81
59
 
60
+ # Returns the compiled instance, so callers serve the one they
61
+ # compiled rather than re-reading @instance — +change!+ can nil it
62
+ # between the two reads (see #call / #recognize_path).
82
63
  def compile!
83
- return if instance
84
-
85
- LOCK.synchronize { compile unless instance }
64
+ @instance || LOCK.synchronize { @instance ||= new }
86
65
  end
87
66
 
88
67
  # see Grape::Router#recognize_path
89
68
  def recognize_path(path)
90
- compile!
91
- instance.router.recognize_path(path)
92
- end
93
-
94
- protected
95
-
96
- def prepare_routes
97
- endpoints.map(&:routes).flatten
69
+ compile!.router.recognize_path(path)
98
70
  end
99
71
 
100
- # Execute first the provided block, then each of the
101
- # block passed in. Allows for simple 'before' setups
102
- # of settings stack pushes.
103
- def nest(*blocks, &block)
104
- blocks.compact!
105
- if blocks.any?
106
- evaluate_as_instance_with_configuration(block) if block
107
- blocks.each { |b| evaluate_as_instance_with_configuration(b) }
108
- reset_validations!
109
- else
110
- instance_eval(&block)
111
- end
112
- end
113
-
114
- def evaluate_as_instance_with_configuration(block, lazy: false)
115
- lazy_block = Grape::Util::LazyBlock.new do |configuration|
116
- value_for_configuration = configuration
117
- self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
118
- response = instance_eval(&block)
119
- self.configuration = value_for_configuration
120
- response
121
- end
122
- if base && base_instance? && lazy
123
- lazy_block
124
- else
125
- lazy_block.evaluate_from(configuration)
126
- end
72
+ # Wipe the compiled API so we can recompile after changes were made.
73
+ def change!
74
+ @instance = nil
127
75
  end
128
76
 
129
- def inherited(subclass)
130
- subclass.reset!
131
- subclass.logger = logger.clone
132
- end
77
+ protected
133
78
 
134
79
  def inherit_settings(other_settings)
135
80
  top_level_setting.inherit_from other_settings.point_in_time_copy
@@ -137,12 +82,20 @@ module Grape
137
82
  # Propagate any inherited params down to our endpoints, and reset any
138
83
  # compiled routes.
139
84
  endpoints.each do |e|
140
- e.inherit_settings(top_level_setting.namespace_stackable)
85
+ e.inherit_settings(top_level_setting)
141
86
  e.reset_routes!
142
87
  end
143
88
 
144
89
  reset_routes!
145
90
  end
91
+
92
+ private
93
+
94
+ def inherited(subclass)
95
+ super
96
+ subclass.reset!
97
+ subclass.logger logger.clone
98
+ end
146
99
  end
147
100
 
148
101
  attr_reader :router
@@ -158,13 +111,23 @@ module Grape
158
111
 
159
112
  @router.compile!
160
113
  @router.freeze
114
+ @cascade = resolve_cascade
161
115
  end
162
116
 
163
117
  # Handle a request. See Rack documentation for what `env` is.
164
118
  def call(env)
165
- result = @router.call(env)
166
- result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
167
- result
119
+ status, headers, response = @router.call(env)
120
+ unless @cascade
121
+ # +merge!+, not +merge+: the latter is a `dup` plus a `merge!`, so the
122
+ # Header built on this line would be allocated only to be discarded.
123
+ # The copy stays because +headers+ can come from a mounted Rack app,
124
+ # which is free to hand back a frozen or shared Hash that the delete
125
+ # below must not reach into.
126
+ headers = Grape::Util::Header.new.merge!(headers)
127
+ headers.delete('X-Cascade')
128
+ end
129
+
130
+ [status, headers, response]
168
131
  end
169
132
 
170
133
  # Some requests may return a HTTP 404 error if grape cannot find a matching
@@ -175,11 +138,13 @@ module Grape
175
138
  # In some applications (e.g. mounting grape on rails), one might need to trap
176
139
  # errors from reaching upstream. This is effectivelly done by unsetting
177
140
  # X-Cascade. Default :cascade is true.
141
+ #
142
+ # Resolved in the constructor rather than per request: answering it walks
143
+ # the whole scope chain twice, and it reads the same settings the routes
144
+ # were compiled and frozen from -- changing those has to go through
145
+ # +change!+, which discards this instance.
178
146
  def cascade?
179
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
180
- return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options)&.key?(:cascade)
181
-
182
- true
147
+ @cascade
183
148
  end
184
149
 
185
150
  reset!
@@ -191,88 +156,40 @@ module Grape
191
156
  # will return an HTTP 405 response for any HTTP method that the resource
192
157
  # cannot handle.
193
158
  def add_head_not_allowed_methods_and_options_methods
194
- versioned_route_configs = collect_route_config_per_pattern
195
159
  # The paths we collected are prepared (cf. Path#prepare), so they
196
160
  # contain already versioning information when using path versioning.
197
- # Disable versioning so adding a route won't prepend versioning
198
- # informations again.
199
- without_root_prefix do
200
- without_versioning do
201
- versioned_route_configs.each do |config|
202
- next if config[:options][:matching_wildchar]
203
-
204
- allowed_methods = config[:methods].dup
205
-
206
- allowed_methods |= [Grape::Http::Headers::HEAD] if !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Grape::Http::Headers::GET)
161
+ all_routes = self.class.endpoints.flat_map(&:routes)
207
162
 
208
- allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
209
-
210
- config[:endpoint].options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
211
-
212
- attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
213
- generate_not_allowed_method(config[:pattern], **attributes)
214
- end
215
- end
216
- end
163
+ collect_route_config_per_pattern(all_routes)
217
164
  end
218
165
 
219
- def collect_route_config_per_pattern
220
- all_routes = self.class.endpoints.map(&:routes).flatten
221
- routes_by_regexp = all_routes.group_by { |route| route.pattern.to_regexp }
166
+ def collect_route_config_per_pattern(all_routes)
167
+ setting = self.class.inheritable_setting
168
+ routes_by_regexp = all_routes.group_by(&:pattern_regexp)
222
169
 
223
170
  # Build the configuration based on the first endpoint and the collection of methods supported.
224
- routes_by_regexp.values.map do |routes|
225
- last_route = routes.last # Most of the configuration is taken from the last endpoint
226
- matching_wildchar = routes.any? { |route| route.request_method == '*' }
227
- {
228
- options: { matching_wildchar: matching_wildchar },
229
- pattern: last_route.pattern,
230
- requirements: last_route.requirements,
231
- path: last_route.origin,
232
- endpoint: last_route.app,
233
- methods: matching_wildchar ? Grape::Http::Headers::SUPPORTED_METHODS : routes.map(&:request_method)
234
- }
235
- end
236
- end
237
-
238
- # Generate a route that returns an HTTP 405 response for a user defined
239
- # path on methods not specified
240
- def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
241
- supported_methods =
242
- if self.class.namespace_inheritable(:do_not_route_options)
243
- Grape::Http::Headers::SUPPORTED_METHODS
244
- else
245
- Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
246
- end
247
- not_allowed_methods = supported_methods - allowed_methods
248
- @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
249
- end
171
+ routes_by_regexp.each_value do |routes|
172
+ next if routes.any? { |route| route.request_method == '*' }
250
173
 
251
- # Allows definition of endpoints that ignore the versioning configuration
252
- # used by the rest of your API.
253
- def without_versioning(&_block)
254
- old_version = self.class.namespace_inheritable(:version)
255
- old_version_options = self.class.namespace_inheritable(:version_options)
174
+ last_route = routes.last # Most of the configuration is taken from the last endpoint
175
+ allowed_methods = routes.map(&:request_method)
176
+ allowed_methods |= [Rack::HEAD] if !setting.do_not_route_head? && allowed_methods.include?(Rack::GET)
256
177
 
257
- self.class.namespace_inheritable_to_nil(:version)
258
- self.class.namespace_inheritable_to_nil(:version_options)
178
+ allow_header = setting.do_not_route_options? ? allowed_methods : [Rack::OPTIONS] | allowed_methods
179
+ last_route.app.options_route_enabled = true unless setting.do_not_route_options? || allowed_methods.include?(Rack::OPTIONS)
259
180
 
260
- yield
261
-
262
- self.class.namespace_inheritable(:version, old_version)
263
- self.class.namespace_inheritable(:version_options, old_version_options)
181
+ greedy_route = Grape::Router::GreedyRoute.new(last_route.pattern, endpoint: last_route.app, allow_header:)
182
+ @router.associate_routes(greedy_route)
183
+ end
264
184
  end
265
185
 
266
- # Allows definition of endpoints that ignore the root prefix used by the
267
- # rest of your API.
268
- def without_root_prefix(&_block)
269
- old_prefix = self.class.namespace_inheritable(:root_prefix)
186
+ # Backs {#cascade?}; called once, from the constructor.
187
+ def resolve_cascade
188
+ setting = self.class.inheritable_setting
189
+ return setting.cascade if setting.cascade_defined?
190
+ return setting.version_options.cascade if setting.version_options
270
191
 
271
- self.class.namespace_inheritable_to_nil(:root_prefix)
272
-
273
- yield
274
-
275
- self.class.namespace_inheritable(:root_prefix, old_prefix)
192
+ true
276
193
  end
277
194
  end
278
195
  end
data/lib/grape/api.rb CHANGED
@@ -1,49 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/router'
4
- require 'grape/api/instance'
5
-
6
3
  module Grape
7
4
  # The API class is the primary entry point for creating Grape APIs. Users
8
5
  # should subclass this class in order to build an API.
9
6
  class API
7
+ # Marks this and every subclass as a mountable Grape app (see Grape::Mountable).
8
+ extend Grape::Mountable
9
+
10
10
  # Class methods that we want to call on the API rather than on the API object
11
- NON_OVERRIDABLE = %i[call call! configuration compile! inherited].freeze
11
+ NON_OVERRIDABLE = %i[base base= base_instance? call change! configuration compile! inherit_settings recognize_path reset! routes top_level_setting= top_level_setting].freeze
12
+
13
+ Helpers = Grape::DSL::Helpers::BaseHelper
12
14
 
13
15
  class Boolean
16
+ VALUES = [true, false].freeze
17
+ private_constant :VALUES
18
+
14
19
  def self.build(val)
15
- return nil if val != true && val != false
20
+ return unless VALUES.include?(val)
16
21
 
17
22
  new
18
23
  end
19
24
  end
20
25
 
21
- class Instance
22
- Boolean = Grape::API::Boolean
23
- end
24
-
25
26
  class << self
26
- attr_accessor :base_instance, :instances
27
+ extend Forwardable
27
28
 
28
- # Rather than initializing an object of type Grape::API, create an object of type Instance
29
- def new(*args, &block)
30
- base_instance.new(*args, &block)
31
- end
29
+ attr_accessor :base_instance, :instances
32
30
 
33
- # When inherited, will create a list of all instances (times the API was mounted)
34
- # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
35
- def inherited(api)
36
- super
31
+ delegate_missing_to :base_instance
37
32
 
38
- api.initial_setup(Grape::API == self ? Grape::API::Instance : @base_instance)
39
- api.override_all_methods!
40
- end
33
+ # This is the interface point between Rack and Grape; it accepts a request
34
+ # from Rack and ultimately returns an array of three values: the status,
35
+ # the headers, and the body. See [the rack specification]
36
+ # (https://github.com/rack/rack/blob/main/SPEC.rdoc) for more.
37
+ # NOTE: This will only be called on an API directly mounted on RACK
38
+ #
39
+ # +inherit_settings+ is left out: it is protected on the base instance, so
40
+ # a delegator would raise NoMethodError just as the missing one does.
41
+ def_delegators :base_instance, :new, :configuration, :call, :change!, :compile!, :recognize_path, :routes,
42
+ :base, :base=, :base_instance?, :reset!, :top_level_setting
41
43
 
42
44
  # Initialize the instance variables on the remountable class, and the base_instance
43
45
  # an instance that will be used to create the set up but will not be mounted
44
46
  def initial_setup(base_instance_parent)
45
47
  @instances = []
46
- @setup = Set.new
48
+ @setup = []
47
49
  @base_parent = base_instance_parent
48
50
  @base_instance = mount_instance
49
51
  end
@@ -51,8 +53,8 @@ module Grape
51
53
  # Redefines all methods so that are forwarded to add_setup and be recorded
52
54
  def override_all_methods!
53
55
  (base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|
54
- define_singleton_method(method_override) do |*args, &block|
55
- add_setup(method_override, *args, &block)
56
+ define_singleton_method(method_override) do |*args, **kwargs, &block|
57
+ add_setup(method: method_override, args:, kwargs:, block:)
56
58
  end
57
59
  end
58
60
  end
@@ -64,138 +66,104 @@ module Grape
64
66
  # `configuration` as normal.
65
67
  def configure
66
68
  config = @base_instance.configuration
67
- if block_given?
68
- yield config
69
- self
70
- else
71
- config
72
- end
73
- end
69
+ return config unless block_given?
74
70
 
75
- # This is the interface point between Rack and Grape; it accepts a request
76
- # from Rack and ultimately returns an array of three values: the status,
77
- # the headers, and the body. See [the rack specification]
78
- # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
79
- # NOTE: This will only be called on an API directly mounted on RACK
80
- def call(*args, &block)
81
- instance_for_rack.call(*args, &block)
82
- end
83
-
84
- # Alleviates problems with autoloading by tring to search for the constant
85
- def const_missing(*args)
86
- if base_instance.const_defined?(*args)
87
- base_instance.const_get(*args)
88
- else
89
- super
90
- end
71
+ yield config
72
+ self
91
73
  end
92
74
 
93
75
  # The remountable class can have a configuration hash to provide some dynamic class-level variables.
94
76
  # For instance, a description could be done using: `desc configuration[:description]` if it may vary
95
77
  # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
96
78
  # too much, you may actually want to provide a new API rather than remount it.
97
- def mount_instance(**opts)
79
+ def mount_instance(configuration: nil)
98
80
  instance = Class.new(@base_parent)
99
- instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
81
+ instance.configuration = Grape::Util::EndpointConfiguration.new(configuration || {})
100
82
  instance.base = self
101
83
  replay_setup_on(instance)
102
84
  instance
103
85
  end
104
86
 
87
+ private
88
+
89
+ # When inherited, will create a list of all instances (times the API was mounted)
90
+ # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
91
+ def inherited(api)
92
+ super
93
+
94
+ api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
95
+ api.override_all_methods!
96
+ end
97
+
105
98
  # Replays the set up to produce an API as defined in this class, can be called
106
99
  # on classes that inherit from Grape::API
107
100
  def replay_setup_on(instance)
108
101
  @setup.each do |setup_step|
109
- replay_step_on(instance, setup_step)
102
+ replay_step_on(instance, **setup_step)
110
103
  end
111
104
  end
112
105
 
113
- def respond_to?(method, include_private = false)
114
- super(method, include_private) || base_instance.respond_to?(method, include_private)
115
- end
116
-
117
- def respond_to_missing?(method, include_private = false)
118
- base_instance.respond_to?(method, include_private)
119
- end
120
-
121
- def method_missing(method, *args, &block)
122
- # If there's a missing method, it may be defined on the base_instance instead.
123
- if respond_to_missing?(method)
124
- base_instance.send(method, *args, &block)
125
- else
126
- super
106
+ # Adds a new stage to the set up require to get a Grape::API up and running
107
+ def add_setup(**step)
108
+ @setup << step
109
+ last_response = nil
110
+ @instances.each do |instance|
111
+ last_response = replay_step_on(instance, **step)
127
112
  end
128
- end
129
113
 
130
- def compile!
131
- require 'grape/eager_load'
132
- instance_for_rack.compile! # See API::Instance.compile!
114
+ refresh_mount_step if step[:method] != :mount
115
+ last_response
133
116
  end
134
117
 
135
- private
118
+ # Updating all previously mounted classes in the case that new methods have been executed.
119
+ def refresh_mount_step
120
+ @setup.each do |setup_step|
121
+ next if setup_step[:method] != :mount
136
122
 
137
- def instance_for_rack
138
- if never_mounted?
139
- base_instance
140
- else
141
- mounted_instances.first
123
+ refresh_mount_step = setup_step.merge(method: :refresh_mounted_api)
124
+ @setup << refresh_mount_step
125
+ @instances.each do |instance|
126
+ replay_step_on(instance, **refresh_mount_step)
127
+ end
142
128
  end
143
129
  end
144
130
 
145
- # Adds a new stage to the set up require to get a Grape::API up and running
146
- def add_setup(method, *args, &block)
147
- setup_step = { method: method, args: args, block: block }
148
- @setup += [setup_step]
149
- last_response = nil
150
- @instances.each do |instance|
151
- last_response = replay_step_on(instance, setup_step)
152
- end
153
- last_response
154
- end
131
+ def replay_step_on(instance, method:, args:, kwargs:, block:)
132
+ return if skip_immediate_run?(instance, args, kwargs)
155
133
 
156
- def replay_step_on(instance, setup_step)
157
- return if skip_immediate_run?(instance, setup_step[:args])
134
+ eval_args = evaluate_arguments(instance.configuration, *args)
135
+ eval_kwargs = kwargs.deep_transform_values { |v| evaluate_arguments(instance.configuration, v).first }
136
+ response = instance.__send__(method, *eval_args, **eval_kwargs, &block)
158
137
 
159
- args = evaluate_arguments(instance.configuration, *setup_step[:args])
160
- response = instance.send(setup_step[:method], *args, &setup_step[:block])
161
- if skip_immediate_run?(instance, [response])
162
- response
163
- else
164
- evaluate_arguments(instance.configuration, response).first
165
- end
138
+ return response if skip_immediate_run?(instance, [response], kwargs)
139
+
140
+ evaluate_arguments(instance.configuration, response).first
166
141
  end
167
142
 
168
143
  # Skips steps that contain arguments to be lazily executed (on re-mount time)
169
- def skip_immediate_run?(instance, args)
144
+ def skip_immediate_run?(instance, args, kwargs)
170
145
  instance.base_instance? &&
171
- (any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) })
146
+ (any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) } || any_lazy?(kwargs.values))
172
147
  end
173
148
 
174
149
  def any_lazy?(args)
175
- args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? }
150
+ args.any?(Grape::Util::Lazy::Base)
176
151
  end
177
152
 
178
153
  def evaluate_arguments(configuration, *args)
179
154
  args.map do |argument|
180
- if argument.respond_to?(:lazy?) && argument.lazy?
155
+ case argument
156
+ when Grape::Util::Lazy::Base
181
157
  argument.evaluate_from(configuration)
182
- elsif argument.is_a?(Hash)
158
+ when Hash
183
159
  argument.transform_values { |value| evaluate_arguments(configuration, value).first }
184
- elsif argument.is_a?(Array)
160
+ when Array
185
161
  evaluate_arguments(configuration, *argument)
186
162
  else
187
163
  argument
188
164
  end
189
165
  end
190
166
  end
191
-
192
- def never_mounted?
193
- mounted_instances.empty?
194
- end
195
-
196
- def mounted_instances
197
- instances - [base_instance]
198
- end
199
167
  end
200
168
  end
201
169
  end