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/lib/grape/parser.rb CHANGED
@@ -2,32 +2,14 @@
2
2
 
3
3
  module Grape
4
4
  module Parser
5
- extend Util::Registrable
5
+ extend Grape::Util::Registry
6
6
 
7
- class << self
8
- def builtin_parsers
9
- @builtin_parsers ||= {
10
- json: Grape::Parser::Json,
11
- jsonapi: Grape::Parser::Json,
12
- xml: Grape::Parser::Xml
13
- }
14
- end
7
+ module_function
15
8
 
16
- def parsers(**options)
17
- builtin_parsers.merge(default_elements).merge!(options[:parsers] || {})
18
- end
9
+ def parser_for(format, parsers = nil)
10
+ return parsers[format] if parsers&.key?(format)
19
11
 
20
- def parser_for(api_format, **options)
21
- spec = parsers(**options)[api_format]
22
- case spec
23
- when nil
24
- nil
25
- when Symbol
26
- method(spec)
27
- else
28
- spec
29
- end
30
- end
12
+ registry[format]
31
13
  end
32
14
  end
33
15
  end
data/lib/grape/path.rb CHANGED
@@ -1,98 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/util/cache'
4
-
5
3
  module Grape
6
- # Represents a path to an endpoint.
7
- class Path
8
- def self.prepare(raw_path, namespace, settings)
9
- Path.new(raw_path, namespace, settings)
10
- end
11
-
12
- attr_reader :raw_path, :namespace, :settings
13
-
14
- def initialize(raw_path, namespace, settings)
15
- @raw_path = raw_path
16
- @namespace = namespace
17
- @settings = settings
18
- end
19
-
20
- def mount_path
21
- settings[:mount_path]
22
- end
23
-
24
- def root_prefix
25
- split_setting(:root_prefix)
26
- end
27
-
28
- def uses_specific_format?
29
- if settings.key?(:format) && settings.key?(:content_types)
30
- (settings[:format] && Array(settings[:content_types]).size == 1)
31
- else
32
- false
33
- end
34
- end
35
-
36
- def uses_path_versioning?
37
- if settings.key?(:version) && settings[:version_options] && settings[:version_options].key?(:using)
38
- (settings[:version] && settings[:version_options][:using] == :path)
39
- else
40
- false
41
- end
42
- end
43
-
44
- def namespace?
45
- namespace&.match?(/^\S/) && namespace != '/'
46
- end
47
-
48
- def path?
49
- raw_path&.match?(/^\S/) && raw_path != '/'
50
- end
51
-
52
- def suffix
53
- if uses_specific_format?
54
- "(.#{settings[:format]})"
55
- elsif !uses_path_versioning? || (namespace? || path?)
56
- '(.:format)'
57
- else
58
- '(/.:format)'
59
- end
60
- end
61
-
62
- def path
63
- Grape::Router.normalize_path(PartsCache[parts])
64
- end
65
-
66
- def path_with_suffix
67
- "#{path}#{suffix}"
68
- end
69
-
70
- def to_s
71
- path_with_suffix
72
- end
73
-
74
- private
75
-
76
- class PartsCache < Grape::Util::Cache
77
- def initialize
78
- @cache = Hash.new do |h, parts|
79
- h[parts] = -parts.join('/')
80
- end
81
- end
82
- end
83
-
84
- def parts
85
- parts = [mount_path, root_prefix].compact
86
- parts << ':version' if uses_path_versioning?
87
- parts << namespace.to_s
88
- parts << raw_path.to_s
89
- parts.flatten.reject { |part| part == '/' }
90
- end
91
-
92
- def split_setting(key)
93
- return if settings[key].nil?
94
-
95
- settings[key].to_s.split('/')
96
- end
97
- end
4
+ # @deprecated +Grape::Path+ moved to {Grape::Router::Pattern::Path}, since it
5
+ # is a router-internal detail that only exists to build a
6
+ # {Grape::Router::Pattern}. Reference the new constant instead.
7
+ Path = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
8
+ 'Grape::Path',
9
+ 'Grape::Router::Pattern::Path',
10
+ Grape.deprecator
11
+ )
98
12
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ # Marks a body as JSON that has already been rendered, so the JSON formatters
5
+ # serve it verbatim instead of encoding it a second time.
6
+ #
7
+ # +Grape::Formatter::Json+ calls +to_json+ on whatever the endpoint returned, so a
8
+ # String holding pre-rendered JSON — a blob cached in Redis, a +json_agg+ column read
9
+ # straight from Postgres, output from another serializer — comes back encoded twice:
10
+ #
11
+ # '{"a":1}' => "\"{\\\"a\\\":1}\""
12
+ #
13
+ # Wrapping the body opts that one response out of encoding:
14
+ #
15
+ # get '/cached' do
16
+ # body Grape::PrecompiledJson.new(Rails.cache.read('payload'))
17
+ # end
18
+ #
19
+ # An Array is joined into a JSON array without its members being parsed, which is
20
+ # what makes a cached collection cheap — N rendered blobs are spliced together
21
+ # rather than round-tripped:
22
+ #
23
+ # Grape::PrecompiledJson.new(['{"id":1}', '{"id":2}']).to_s # => '[{"id":1},{"id":2}]'
24
+ #
25
+ # +Array#join+ calls +to_s+ on each member, so members may themselves be
26
+ # +PrecompiledJson+ instances.
27
+ #
28
+ # Wrap only the whole body. A wrapper nested inside a Hash or Array that is then
29
+ # handed to an encoder is serialized as an ordinary object — +ActiveSupport::JSON+
30
+ # renders it through +as_json+ as +{"value":"{\"a\":1}"}+ — because no encoder knows
31
+ # to unwrap it. Nothing checks that the String actually holds JSON; that is the
32
+ # caller's responsibility.
33
+ class PrecompiledJson
34
+ # @param value [String, Array<String>] pre-rendered JSON
35
+ def initialize(value)
36
+ @value = value
37
+ end
38
+
39
+ # @return [String] the JSON to serve
40
+ # @raise [Grape::Exceptions::InvalidFormatter] if the value is neither a String
41
+ # nor an Array; the formatter middleware turns this into a 500 rather than
42
+ # letting a body Rack cannot serve reach the SPEC check.
43
+ def to_s
44
+ return @value if @value.is_a?(String)
45
+ return "[#{@value.join(',')}]" if @value.is_a?(Array)
46
+
47
+ raise Grape::Exceptions::InvalidFormatter.new(@value.class, 'json')
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ class Railtie < ::Rails::Railtie
5
+ initializer 'grape.deprecator' do |app|
6
+ app.deprecators[:grape] = Grape.deprecator
7
+ end
8
+ end
9
+ end
data/lib/grape/request.rb CHANGED
@@ -1,53 +1,227 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/util/lazy_object'
4
-
5
3
  module Grape
6
4
  class Request < Rack::Request
7
- HTTP_PREFIX = 'HTTP_'
5
+ # Based on rack 3 KNOWN_HEADERS
6
+ # https://github.com/rack/rack/blob/4f15e7b814922af79605be4b02c5b7c3044ba206/lib/rack/headers.rb#L10
7
+
8
+ KNOWN_HEADERS = %w[
9
+ Accept
10
+ Accept-CH
11
+ Accept-Encoding
12
+ Accept-Language
13
+ Accept-Patch
14
+ Accept-Ranges
15
+ Accept-Version
16
+ Access-Control-Allow-Credentials
17
+ Access-Control-Allow-Headers
18
+ Access-Control-Allow-Methods
19
+ Access-Control-Allow-Origin
20
+ Access-Control-Expose-Headers
21
+ Access-Control-Max-Age
22
+ Age
23
+ Allow
24
+ Alt-Svc
25
+ Authorization
26
+ Cache-Control
27
+ Client-Ip
28
+ Connection
29
+ Content-Disposition
30
+ Content-Encoding
31
+ Content-Language
32
+ Content-Length
33
+ Content-Location
34
+ Content-MD5
35
+ Content-Range
36
+ Content-Security-Policy
37
+ Content-Security-Policy-Report-Only
38
+ Content-Type
39
+ Cookie
40
+ Date
41
+ Delta-Base
42
+ Dnt
43
+ ETag
44
+ Expect-CT
45
+ Expires
46
+ Feature-Policy
47
+ Forwarded
48
+ Host
49
+ If-Modified-Since
50
+ If-None-Match
51
+ IM
52
+ Last-Modified
53
+ Link
54
+ Location
55
+ NEL
56
+ P3P
57
+ Permissions-Policy
58
+ Pragma
59
+ Preference-Applied
60
+ Proxy-Authenticate
61
+ Public-Key-Pins
62
+ Range
63
+ Referer
64
+ Referrer-Policy
65
+ Refresh
66
+ Report-To
67
+ Retry-After
68
+ Sec-Fetch-Dest
69
+ Sec-Fetch-Mode
70
+ Sec-Fetch-Site
71
+ Sec-Fetch-User
72
+ Server
73
+ Set-Cookie
74
+ Status
75
+ Strict-Transport-Security
76
+ Timing-Allow-Origin
77
+ Tk
78
+ Trailer
79
+ Transfer-Encoding
80
+ Upgrade
81
+ Upgrade-Insecure-Requests
82
+ User-Agent
83
+ Vary
84
+ Version
85
+ Via
86
+ Warning
87
+ WWW-Authenticate
88
+ X-Accel-Buffering
89
+ X-Accel-Charset
90
+ X-Accel-Expires
91
+ X-Accel-Limit-Rate
92
+ X-Accel-Mapping
93
+ X-Accel-Redirect
94
+ X-Access-Token
95
+ X-Auth-Request-Access-Token
96
+ X-Auth-Request-Email
97
+ X-Auth-Request-Groups
98
+ X-Auth-Request-Preferred-Username
99
+ X-Auth-Request-Redirect
100
+ X-Auth-Request-Token
101
+ X-Auth-Request-User
102
+ X-Cascade
103
+ X-Client-Ip
104
+ X-Content-Duration
105
+ X-Content-Security-Policy
106
+ X-Content-Type-Options
107
+ X-Correlation-Id
108
+ X-Download-Options
109
+ X-Forwarded-Access-Token
110
+ X-Forwarded-Email
111
+ X-Forwarded-For
112
+ X-Forwarded-Groups
113
+ X-Forwarded-Host
114
+ X-Forwarded-Port
115
+ X-Forwarded-Preferred-Username
116
+ X-Forwarded-Proto
117
+ X-Forwarded-Scheme
118
+ X-Forwarded-Ssl
119
+ X-Forwarded-Uri
120
+ X-Forwarded-User
121
+ X-Frame-Options
122
+ X-HTTP-Method-Override
123
+ X-Permitted-Cross-Domain-Policies
124
+ X-Powered-By
125
+ X-Real-IP
126
+ X-Redirect-By
127
+ X-Request-Id
128
+ X-Requested-With
129
+ X-Runtime
130
+ X-Sendfile
131
+ X-Sendfile-Type
132
+ X-UA-Compatible
133
+ X-WebKit-CS
134
+ X-XSS-Protection
135
+ ].to_h do |header|
136
+ ["HTTP_#{header.upcase.tr('-', '_')}", header]
137
+ end.freeze
8
138
 
9
139
  alias rack_params params
140
+ alias rack_cookies cookies
10
141
 
11
- def initialize(env, **options)
12
- extend options[:build_params_with] || Grape.config.param_builder
142
+ def initialize(env, build_params_with: nil)
13
143
  super(env)
144
+ @build_params_with = build_params_with
14
145
  end
15
146
 
16
147
  def params
17
- @params ||= build_params
18
- rescue EOFError
19
- raise Grape::Exceptions::EmptyMessageBody.new(content_type)
20
- rescue Rack::Multipart::MultipartPartLimitError
21
- raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
148
+ @params ||= make_params
22
149
  end
23
150
 
24
151
  def headers
25
152
  @headers ||= build_headers
26
153
  end
27
154
 
155
+ def cookies
156
+ @cookies ||= Grape::Cookies.new(rack_cookies)
157
+ end
158
+
159
+ # True once the cookie jar has been materialized (a cookie was read or
160
+ # written this request). Lets the endpoint skip response-cookie flushing,
161
+ # and the Grape::Cookies allocation it triggers, when no cookie was
162
+ # touched on the request.
163
+ def cookies?
164
+ !@cookies.nil?
165
+ end
166
+
28
167
  private
29
168
 
30
- def grape_routing_args
31
- args = env[Grape::Env::GRAPE_ROUTING_ARGS].dup
32
- # preserve version from query string parameters
33
- args.delete(:version)
34
- args.delete(:route_info)
35
- args
169
+ # Resolved on first use rather than in the constructor: a request that never
170
+ # reads +params+ -- an endpoint with no validations whose block does not ask
171
+ # for them -- pays neither the registry lookup nor the global config read,
172
+ # and every request builds a Grape::Request.
173
+ def params_builder
174
+ @params_builder ||= Grape::ParamsBuilder.params_builder_for(@build_params_with || Grape.config[:param_builder])
36
175
  end
37
176
 
38
- def build_headers
39
- Grape::Util::LazyObject.new do
40
- env.each_pair.with_object({}) do |(k, v), headers|
41
- next unless k.to_s.start_with? HTTP_PREFIX
177
+ def make_params
178
+ params = params_builder.call(rack_params)
179
+ filtered = routing_args_as_params(env[Grape::Env::GRAPE_ROUTING_ARGS])
180
+ return params if filtered.blank?
42
181
 
43
- transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
44
- headers[transformed_header] = v
45
- end
46
- end
182
+ params.deep_merge!(filtered)
183
+ rescue *Grape::RACK_ERRORS
184
+ raise Grape::Exceptions::RequestError
47
185
  end
48
186
 
49
- def transform_header(header)
50
- -header[5..].split('_').each(&:capitalize!).join('-')
187
+ # The routing args carry two things that are not request params:
188
+ # +:route_info+, which is always Grape's own, and +:version+, which is only
189
+ # Grape's own when the API declared a version — that is captured as a path
190
+ # segment and exposed through +env['api.version']+ instead.
191
+ #
192
+ # An API that declares no version can legitimately name a param +:version+
193
+ # (`route_param :version`, `get '/:version'`), and that capture belongs to
194
+ # the application. Dropping it unconditionally left `params[:version]` nil
195
+ # on a route that had matched, losing the segment silently.
196
+ def routing_args_as_params(routing_args)
197
+ return if routing_args.nil?
198
+ return routing_args.except(:version, :route_info) if grape_owns_version?(routing_args)
199
+
200
+ routing_args.except(:route_info)
201
+ end
202
+
203
+ # A route reports a +version+ only when the API declared one, which is the
204
+ # case where the captured segment is Grape's rather than the application's.
205
+ def grape_owns_version?(routing_args)
206
+ return false unless routing_args.key?(:version)
207
+
208
+ route = routing_args[:route_info]
209
+ route.respond_to?(:version) && !route.version.nil?
210
+ end
211
+
212
+ # Uses a plain `each_header` block instead of `each_header.with_object`:
213
+ # `with_object` can only pass the block one value plus the memo, so the
214
+ # `k, v` pair would be boxed into a throwaway Array on every header. A
215
+ # two-arg block receives `k`/`v` directly and allocates nothing extra.
216
+ def build_headers
217
+ headers = Grape::Util::Header.new
218
+ each_header do |k, v|
219
+ next unless k.start_with? 'HTTP_'
220
+
221
+ transformed_header = KNOWN_HEADERS.fetch(k) { -k[5..].tr('_', '-').downcase }
222
+ headers[transformed_header] = v
223
+ end
224
+ headers
51
225
  end
52
226
  end
53
227
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ class Router
5
+ class BaseRoute
6
+ extend Forwardable
7
+
8
+ delegate_missing_to :@options
9
+
10
+ attr_reader :options, :pattern, :prefix, :settings, :namespace
11
+
12
+ # +version+, +anchor+ and +requirements+ shape the matcher, so they are
13
+ # read from the pattern rather than stored again on the route.
14
+ def_delegators :@pattern, :path, :origin, :version, :anchor, :requirements
15
+ def_delegators :@options, :description, *Grape::Util::ApiDescription::DSL_METHODS
16
+
17
+ def initialize(pattern, options = {}, namespace: nil, prefix: nil, settings: nil)
18
+ @pattern = pattern
19
+ @options = options.is_a?(ActiveSupport::OrderedOptions) ? options : ActiveSupport::OrderedOptions.new.update(options)
20
+ @namespace = namespace
21
+ @prefix = prefix
22
+ @settings = settings
23
+ end
24
+
25
+ # +success+ and +failure+ are the +desc+ DSL's names for +entity+ and
26
+ # +http_codes+: the block form writes the canonical key, a keyword option
27
+ # keeps the name it was written with, so both spellings are read here.
28
+ # Without this they resolve through +delegate_missing_to+, which answers
29
+ # nil for whichever of the two keys the description did not use.
30
+ def success
31
+ @options[:entity] || @options[:success]
32
+ end
33
+
34
+ def failure
35
+ @options[:http_codes] || @options[:failure]
36
+ end
37
+
38
+ # @deprecated Use {#default_response}, the name grape-swagger asks for.
39
+ # This one has to be written out rather than delegated like the rest:
40
+ # an ActiveSupport::OrderedOptions answers an unknown name with that
41
+ # key's value, but +default+ is not unknown to it — it is +Hash#default+,
42
+ # the Hash's own default value — so a delegator would report nil for
43
+ # every route.
44
+ def default
45
+ Grape.deprecator.warn('`Grape::Router::Route#default` is deprecated. Use `#default_response` instead.')
46
+ default_response
47
+ end
48
+
49
+ # Assigned eagerly in {#to_regexp} (router compilation) rather than
50
+ # memoized here: this reader is called from request-time route matching
51
+ # on instances shared across threads, so it must not write state.
52
+ attr_reader :regexp_capture_index
53
+
54
+ # The number of the group this route occupies in the union the router
55
+ # compiled it into. Only the union can say what it is, so it is written
56
+ # back by {Router#compile!} right after building one, and like
57
+ # +regexp_capture_index+ it is never assigned at request time.
58
+ attr_reader :regexp_capture_group
59
+
60
+ def pattern_regexp
61
+ @pattern.to_regexp
62
+ end
63
+
64
+ def to_regexp(index)
65
+ @regexp_capture_index = CaptureIndexCache[index]
66
+ Regexp.new("(?<#{regexp_capture_index}>#{pattern_regexp})")
67
+ end
68
+
69
+ # @api private
70
+ # @see #regexp_capture_group
71
+ def resolve_capture_group!(union_named_captures)
72
+ @regexp_capture_group = union_named_captures.fetch(regexp_capture_index).first
73
+ end
74
+
75
+ class CaptureIndexCache < Grape::Util::Cache
76
+ def initialize
77
+ super
78
+ @cache = Hash.new do |h, index|
79
+ h[index] = "_#{index}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Act like a Grape::Router::Route but for greedy_match
4
+ # see @neutral_map
5
+
6
+ module Grape
7
+ class Router
8
+ class GreedyRoute < BaseRoute
9
+ extend Forwardable
10
+
11
+ def_delegators :@endpoint, :call
12
+
13
+ attr_reader :endpoint, :allow_header
14
+
15
+ def initialize(pattern, endpoint:, allow_header:)
16
+ super(pattern)
17
+ @endpoint = endpoint
18
+ @allow_header = allow_header
19
+ end
20
+
21
+ def params
22
+ nil
23
+ end
24
+
25
+ def params_for(_input)
26
+ nil
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ class Router
5
+ # Grape-style path patterns for Mustermann: `:param`, `*splat`, `{name}` /
6
+ # `{+splat}`, `( )` optionals, `|`, and an Integer digit-only constraint
7
+ # (driven by Grape's `params` option).
8
+ #
9
+ # Inlined from the mustermann-grape gem (MIT) by namusyaka, Konstantin Haase
10
+ # and Daniel Doubrovkine. Grape instantiates this class directly (see
11
+ # {Grape::Router::Pattern}), so unlike the gem it is not registered as a
12
+ # Mustermann `type: :grape`.
13
+ class MustermannPattern < ::Mustermann::AST::Pattern
14
+ supported_options :params
15
+
16
+ on(nil, '?', ')') { |c| unexpected(c) }
17
+
18
+ on('*') { |_c| scan(/\w+/) ? node(:named_splat, buffer.matched) : node(:splat) }
19
+ on(':') do |_c|
20
+ param_name = scan(/\w+/)
21
+ # Integer params (declared via Grape's `params` option) match digits only;
22
+ # any other capture matches a single path segment (anything but / ? # .).
23
+ param_type = pattern&.options&.dig(:params, param_name, :type)
24
+ constraint = param_type == 'Integer' ? /\d/ : '[^/?#.]'
25
+ node(:capture, param_name, constraint:) { scan(/\w+/) }
26
+ end
27
+ on('\\') { |_c| node(:char, expect(/./)) }
28
+ on('(') { |_c| node(:optional, node(:group) { read unless scan(')') }) }
29
+ on('|') { |_c| node(:or) }
30
+
31
+ on('{') do |_c|
32
+ type = scan('+') ? :named_splat : :capture
33
+ name = expect(/[\w.]+/)
34
+ type = :splat if (type == :named_splat) && (name == 'splat')
35
+ expect('}')
36
+ node(type, name)
37
+ end
38
+
39
+ suffix('?') do |_c, element|
40
+ node(:optional, element)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ class Router
5
+ class Pattern
6
+ # Assembles the path template a {Pattern} compiles into a matcher. It turns
7
+ # a raw path plus the path-settings snapshot taken by
8
+ # {Grape::Util::InheritableSetting#path_settings} (mount paths, root
9
+ # prefix, path-versioning and format) into an +origin+ (the route prefix)
10
+ # and a +suffix+ (the format segment). {Pattern.build} is the entry point
11
+ # that wires this into pattern construction.
12
+ class Path
13
+ DEFAULT_FORMAT_SEGMENT = '(/.:format)'
14
+ NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT = '(.:format)'
15
+ VERSION_SEGMENT = ':version'
16
+
17
+ attr_reader :origin, :suffix
18
+
19
+ def initialize(raw_path, raw_namespace, settings)
20
+ @origin = PartsCache[build_parts(raw_path, raw_namespace, settings)]
21
+ @suffix = build_suffix(raw_path, raw_namespace, settings)
22
+ end
23
+
24
+ def to_s
25
+ "#{origin}#{suffix}"
26
+ end
27
+
28
+ private
29
+
30
+ def build_suffix(raw_path, raw_namespace, settings)
31
+ return "(.#{settings.format})" if uses_specific_format?(settings)
32
+ return NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT if !uses_path_versioning?(settings) || valid_part?(raw_namespace) || valid_part?(raw_path)
33
+
34
+ DEFAULT_FORMAT_SEGMENT
35
+ end
36
+
37
+ def build_parts(raw_path, raw_namespace, settings)
38
+ parts = []
39
+ add_part(parts, settings.mount_path)
40
+ add_part(parts, settings.root_prefix)
41
+ parts << VERSION_SEGMENT if uses_path_versioning?(settings)
42
+ add_part(parts, raw_namespace)
43
+ add_part(parts, raw_path)
44
+ parts
45
+ end
46
+
47
+ def add_part(parts, value)
48
+ parts << value if value && not_slash?(value)
49
+ end
50
+
51
+ def not_slash?(value)
52
+ value != '/'
53
+ end
54
+
55
+ def uses_specific_format?(settings)
56
+ settings.format && Array(settings.content_types).size == 1
57
+ end
58
+
59
+ def uses_path_versioning?(settings)
60
+ settings.version && settings.version_options&.using == :path
61
+ end
62
+
63
+ def valid_part?(part)
64
+ part&.match?(/\A\S/) && not_slash?(part)
65
+ end
66
+
67
+ class PartsCache < Grape::Util::Cache
68
+ def initialize
69
+ super
70
+ @cache = Hash.new do |h, parts|
71
+ h[parts] = Grape::Util::PathNormalizer.call(parts.join('/'))
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end