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
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/middleware/base'
4
- require 'grape/middleware/versioner/parse_media_type_patch'
5
-
6
3
  module Grape
7
4
  module Middleware
8
5
  module Versioner
@@ -25,168 +22,86 @@ module Grape
25
22
  # X-Cascade header to alert Grape::Router to attempt the next matched
26
23
  # route.
27
24
  class Header < Base
28
- VENDOR_VERSION_HEADER_REGEX =
29
- /\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze
30
-
31
- HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#{Regexp.last_match(0)}\^]+/.freeze
32
- HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))+/.freeze
33
-
34
25
  def before
35
- strict_header_checks if strict?
36
-
37
- if media_type || env[Grape::Env::GRAPE_ALLOWED_METHODS]
38
- media_type_header_handler
39
- elsif headers_contain_wrong_vendor?
40
- fail_with_invalid_accept_header!('API vendor not found.')
41
- elsif headers_contain_wrong_version?
42
- fail_with_invalid_version_header!('API version not found.')
26
+ match_best_quality_media_type! do |media_type|
27
+ env.update(
28
+ Grape::Env::API_TYPE => media_type.type,
29
+ Grape::Env::API_SUBTYPE => media_type.subtype,
30
+ Grape::Env::API_VENDOR => media_type.vendor,
31
+ Grape::Env::API_VERSION => media_type.version,
32
+ Grape::Env::API_FORMAT => media_type.format
33
+ )
43
34
  end
44
35
  end
45
36
 
46
37
  private
47
38
 
48
- def strict_header_checks
49
- strict_accept_header_presence_check
50
- strict_version_vendor_accept_header_presence_check
51
- end
52
-
53
- def strict_accept_header_presence_check
54
- return unless header.qvalues.empty?
55
-
56
- fail_with_invalid_accept_header!('Accept header must be set.')
57
- end
58
-
59
- def strict_version_vendor_accept_header_presence_check
60
- return if versions.blank? || an_accept_header_with_version_and_vendor_is_present?
61
-
62
- fail_with_invalid_accept_header!('API vendor or version not found.')
63
- end
64
-
65
- def an_accept_header_with_version_and_vendor_is_present?
66
- header.qvalues.keys.any? do |h|
67
- VENDOR_VERSION_HEADER_REGEX.match?(h.sub('application/', ''))
68
- end
69
- end
39
+ def match_best_quality_media_type!
40
+ return unless vendor
70
41
 
71
- def header
72
- @header ||= rack_accept_header
73
- end
42
+ strict_header_checks!
43
+ media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types)
44
+ return yield media_type if media_type
74
45
 
75
- def media_type
76
- @media_type ||= header.best_of(available_media_types)
46
+ fail!
77
47
  end
78
48
 
79
- def media_type_header_handler
80
- type, subtype = Rack::Accept::Header.parse_media_type(media_type)
81
- env[Grape::Env::API_TYPE] = type
82
- env[Grape::Env::API_SUBTYPE] = subtype
83
-
84
- return unless VENDOR_VERSION_HEADER_REGEX =~ subtype
85
-
86
- env[Grape::Env::API_VENDOR] = Regexp.last_match[1]
87
- env[Grape::Env::API_VERSION] = Regexp.last_match[2]
88
- # weird that Grape::Middleware::Formatter also does this
89
- env[Grape::Env::API_FORMAT] = Regexp.last_match[3]
49
+ def accept_header
50
+ env['HTTP_ACCEPT']
90
51
  end
91
52
 
92
- def fail_with_invalid_accept_header!(message)
93
- raise Grape::Exceptions::InvalidAcceptHeader
94
- .new(message, error_headers)
95
- end
53
+ def strict_header_checks!
54
+ return unless strict
96
55
 
97
- def fail_with_invalid_version_header!(message)
98
- raise Grape::Exceptions::InvalidVersionHeader
99
- .new(message, error_headers)
56
+ accept_header_check!
57
+ version_and_vendor_check!
100
58
  end
101
59
 
102
- def available_media_types
103
- [].tap do |available_media_types|
104
- content_types.each_key do |extension|
105
- versions.reverse_each do |version|
106
- available_media_types << "application/vnd.#{vendor}-#{version}+#{extension}"
107
- available_media_types << "application/vnd.#{vendor}-#{version}"
108
- end
109
- available_media_types << "application/vnd.#{vendor}+#{extension}"
110
- end
111
-
112
- available_media_types << "application/vnd.#{vendor}"
113
- available_media_types.concat(content_types.values.flatten)
114
- end
115
- end
60
+ def accept_header_check!
61
+ return if accept_header.present?
116
62
 
117
- def headers_contain_wrong_vendor?
118
- header.values.all? do |header_value|
119
- vendor?(header_value) && request_vendor(header_value) != vendor
120
- end
63
+ invalid_accept_header!('Accept header must be set.')
121
64
  end
122
65
 
123
- def headers_contain_wrong_version?
124
- header.values.all? do |header_value|
125
- version?(header_value) && versions.exclude?(request_version(header_value))
126
- end
127
- end
66
+ def version_and_vendor_check!
67
+ return if versions.blank? || version_and_vendor?
128
68
 
129
- def rack_accept_header
130
- Rack::Accept::MediaType.new env[Grape::Http::Headers::HTTP_ACCEPT]
131
- rescue RuntimeError => e
132
- fail_with_invalid_accept_header!(e.message)
69
+ invalid_accept_header!('API vendor or version not found.')
133
70
  end
134
71
 
135
- def versions
136
- options[:versions] || []
72
+ def q_values_mime_types
73
+ @q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
137
74
  end
138
75
 
139
- def vendor
140
- version_options && version_options[:vendor]
76
+ def version_and_vendor?
77
+ q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
141
78
  end
142
79
 
143
- def strict?
144
- version_options && version_options[:strict]
80
+ def invalid_accept_header!(message)
81
+ raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
145
82
  end
146
83
 
147
- def version_options
148
- options[:version_options]
84
+ def invalid_version_header!(message)
85
+ raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
149
86
  end
150
87
 
151
- # By default those errors contain an `X-Cascade` header set to `pass`,
152
- # which allows nesting and stacking of routes
153
- # (see Grape::Router for more
154
- # information). To prevent # this behavior, and not add the `X-Cascade`
155
- # header, one can set the `:cascade` option to `false`.
156
- def cascade?
157
- if version_options&.key?(:cascade)
158
- version_options[:cascade]
159
- else
160
- true
161
- end
162
- end
88
+ def fail!
89
+ return if env[Grape::Env::GRAPE_ALLOWED_METHODS].present?
163
90
 
164
- def error_headers
165
- cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
91
+ media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
92
+ vendor_not_found!(media_types) || version_not_found!(media_types)
166
93
  end
167
94
 
168
- # @param [String] media_type a content type
169
- # @return [Boolean] whether the content type sets a vendor
170
- def vendor?(media_type)
171
- _, subtype = Rack::Accept::Header.parse_media_type(media_type)
172
- subtype.present? && subtype[HAS_VENDOR_REGEX]
173
- end
95
+ def vendor_not_found!(media_types)
96
+ return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
174
97
 
175
- def request_vendor(media_type)
176
- _, subtype = Rack::Accept::Header.parse_media_type(media_type)
177
- subtype.match(VENDOR_VERSION_HEADER_REGEX)[1]
98
+ invalid_accept_header!('API vendor not found.')
178
99
  end
179
100
 
180
- def request_version(media_type)
181
- _, subtype = Rack::Accept::Header.parse_media_type(media_type)
182
- subtype.match(VENDOR_VERSION_HEADER_REGEX)[2]
183
- end
101
+ def version_not_found!(media_types)
102
+ return unless media_types.all? { |media_type| media_type&.version && versions && !versions.include?(media_type.version) }
184
103
 
185
- # @param [String] media_type a content type
186
- # @return [Boolean] whether the content type sets an API version
187
- def version?(media_type)
188
- _, subtype = Rack::Accept::Header.parse_media_type(media_type)
189
- subtype.present? && subtype[HAS_VERSION_REGEX]
104
+ invalid_version_header!('API version not found.')
190
105
  end
191
106
  end
192
107
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/middleware/base'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Versioner
@@ -21,31 +19,12 @@ module Grape
21
19
  #
22
20
  # env['api.version'] => 'v1'
23
21
  class Param < Base
24
- def default_options
25
- {
26
- version_options: {
27
- parameter: 'apiver'
28
- }
29
- }
30
- end
31
-
32
22
  def before
33
- potential_version = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[paramkey]
34
- return if potential_version.nil?
35
-
36
- throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
37
- env[Grape::Env::API_VERSION] = potential_version
38
- env[Grape::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Env::RACK_REQUEST_QUERY_HASH
39
- end
40
-
41
- private
42
-
43
- def paramkey
44
- version_options[:parameter] || default_options[:version_options][:parameter]
45
- end
23
+ potential_version = query_params[parameter]
24
+ return if potential_version.blank?
46
25
 
47
- def version_options
48
- options[:version_options]
26
+ version_not_found! unless potential_version_match?(potential_version)
27
+ env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter)
49
28
  end
50
29
  end
51
30
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/middleware/base'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Versioner
@@ -19,44 +17,69 @@ module Grape
19
17
  # env['api.version'] => 'v1'
20
18
  #
21
19
  class Path < Base
22
- def default_options
23
- {
24
- pattern: /.*/i
25
- }
20
+ def initialize(app, **options)
21
+ super
22
+ @prefixes = [mount_path, Grape::Util::PathNormalizer.call(prefix)].select { |p| p.present? && p != '/' }.freeze
26
23
  end
27
24
 
28
25
  def before
29
- path = env[Grape::Http::Headers::PATH_INFO].dup
30
- path.sub!(mount_path, '') if mounted_path?(path)
26
+ routed = routed_version
27
+ return env[Grape::Env::API_VERSION] = routed if routed
31
28
 
32
- if prefix && path.index(prefix) == 0 # rubocop:disable all
33
- path.sub!(prefix, '')
34
- path = Grape::Router.normalize_path(path)
35
- end
29
+ path_info = env[Grape::Env::GRAPE_NORMALIZED_PATH] || Grape::Util::PathNormalizer.call(env[Rack::PATH_INFO])
30
+ return if path_info == '/'
36
31
 
37
- pieces = path.split('/')
38
- potential_version = pieces[1]
39
- return unless potential_version&.match?(options[:pattern])
32
+ # `each` rather than `reduce`: Array does not override Enumerable's,
33
+ # so the generic accumulator path costs more than the work it drives
34
+ # on a list that holds at most a mount path and a prefix — and most
35
+ # often nothing at all.
36
+ @prefixes.each { |prefix| path_info = path_info.delete_prefix(prefix) if path_info.start_with?(prefix) }
40
37
 
41
- throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
42
- env[Grape::Env::API_VERSION] = potential_version
38
+ slash_position = path_info.index('/', 1) # omit the first one
39
+ return version_from_first_segment(path_info, slash_position) if slash_position
40
+
41
+ version_from_only_segment(path_info)
43
42
  end
44
43
 
45
44
  private
46
45
 
47
- def mounted_path?(path)
48
- return false unless mount_path && path.start_with?(mount_path)
46
+ # Under path versioning every route pattern carries the version as a
47
+ # named capture (Pattern::Path#build_parts inserts it), constrained to
48
+ # the declared versions -- so the router has already sliced the segment
49
+ # out and validated it, and re-deriving it from PATH_INFO would repeat
50
+ # the normalize, prefix-strip and slice below for the same answer.
51
+ #
52
+ # Nil, and the path parsed as before, when there are no routing args at
53
+ # all (the middleware used outside a Grape router) and for the greedy
54
+ # routes behind auto-OPTIONS and 405, which capture nothing. An Array
55
+ # means the route declared a +:version+ segment of its own on top of the
56
+ # versioning one: Mustermann then reports the capture as every position
57
+ # it matched rather than the single segment recorded here, so that too
58
+ # is left to the path parse.
59
+ def routed_version
60
+ version = env[Grape::Env::GRAPE_ROUTING_ARGS]&.[](:version)
61
+ version if version.is_a?(String)
62
+ end
49
63
 
50
- rest = path.slice(mount_path.length..-1)
51
- rest.start_with?('/') || rest.empty?
64
+ def version_from_first_segment(path_info, slash_position)
65
+ potential_version = path_info[1..(slash_position - 1)]
66
+ version_not_found! unless potential_version_match?(potential_version)
67
+ env[Grape::Env::API_VERSION] = potential_version
52
68
  end
53
69
 
54
- def mount_path
55
- @mount_path ||= options[:mount_path] && options[:mount_path] != '/' ? options[:mount_path] : ''
70
+ # The path is a single segment (e.g. `GET /v1` — the root route of a
71
+ # path-versioned API). Nothing follows to disambiguate a version from a
72
+ # plain path or from a `.format` suffix (`/v1.json`), so the version is
73
+ # only recorded on an exact match against the declared versions, and an
74
+ # unmatched segment is left for the router to resolve — never a 404.
75
+ def version_from_only_segment(path_info)
76
+ candidate = path_info[1..]
77
+ candidate = candidate[0...candidate.rindex('.')] while candidate.include?('.') && !declared_version?(candidate)
78
+ env[Grape::Env::API_VERSION] = candidate if declared_version?(candidate)
56
79
  end
57
80
 
58
- def prefix
59
- Grape::Router.normalize_path(options[:prefix].to_s) if options[:prefix]
81
+ def declared_version?(candidate)
82
+ versions.present? && versions.include?(candidate)
60
83
  end
61
84
  end
62
85
  end
@@ -4,30 +4,23 @@
4
4
  # on the requests. The current methods for determining version are:
5
5
  #
6
6
  # :header - version from HTTP Accept header.
7
+ # :accept_version_header - version from HTTP Accept-Version header
7
8
  # :path - version from uri. e.g. /v1/resource
8
9
  # :param - version from uri query string, e.g. /v1/resource?apiver=v1
9
- #
10
10
  # See individual classes for details.
11
11
  module Grape
12
12
  module Middleware
13
13
  module Versioner
14
+ extend Grape::Util::Registry
15
+
14
16
  module_function
15
17
 
16
- # @param strategy [Symbol] :path, :header or :param
18
+ # @param strategy [Symbol] :path, :header, :accept_version_header or :param
17
19
  # @return a middleware class based on strategy
18
20
  def using(strategy)
19
- case strategy
20
- when :path
21
- Path
22
- when :header
23
- Header
24
- when :param
25
- Param
26
- when :accept_version_header
27
- AcceptVersionHeader
28
- else
29
- raise Grape::Exceptions::InvalidVersionerOption.new(strategy)
30
- end
21
+ raise Grape::Exceptions::InvalidVersionerOption, strategy unless registry.key?(strategy)
22
+
23
+ registry[strategy]
31
24
  end
32
25
  end
33
26
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ # Marker module for a mountable Grape application. Both {Grape::API} (the
5
+ # remountable user-facing class) and {Grape::API::Instance} (the compiled
6
+ # engine) extend it, so a mounted Grape app can be told apart from a bare
7
+ # Rack app with `is_a?(Grape::Mountable)` — a single, explicit predicate
8
+ # rather than duck-typing on an incidental internal method such as
9
+ # `inheritable_setting`.
10
+ #
11
+ # `Grape::API` and `Grape::API::Instance` are not related by inheritance and
12
+ # do not even respond to the same methods (the former to `mount_instance`,
13
+ # the latter to `inheritable_setting`/`endpoints`), so there is no common
14
+ # ancestor to key an `is_a?` check on without this marker.
15
+ #
16
+ # It answers identity only ("is this a Grape app?"). Capability checks that
17
+ # go on to call a stage-specific method — e.g. `respond_to?(:endpoints)`
18
+ # before reading `endpoints` — must stay as they are, since a `Mountable`
19
+ # does not necessarily respond to every such method.
20
+ module Mountable
21
+ end
22
+ end
@@ -1,45 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/util/cache'
4
-
5
3
  module Grape
6
4
  # A container for endpoints or other namespaces, which allows for both
7
5
  # logical grouping of endpoints as well as sharing common configuration.
8
6
  # May also be referred to as group, segment, or resource.
9
7
  class Namespace
10
- attr_reader :space, :options
8
+ attr_reader :space, :requirements, :options
11
9
 
12
10
  # @param space [String] the name of this namespace
13
11
  # @param options [Hash] options hash
14
12
  # @option options :requirements [Hash] param-regex pairs, all of which must
15
13
  # be met by a request's params for all endpoints in this namespace, or
16
14
  # validation will fail and return a 422.
17
- def initialize(space, **options)
15
+ def initialize(space, requirements: nil, **options)
18
16
  @space = space.to_s
17
+ @requirements = requirements
19
18
  @options = options
20
19
  end
21
20
 
22
- # Retrieves the requirements from the options hash, if given.
23
- # @return [Hash]
24
- def requirements
25
- options[:requirements] || {}
26
- end
27
-
28
21
  # (see ::joined_space_path)
29
22
  def self.joined_space(settings)
30
23
  settings&.map(&:space)
31
24
  end
32
25
 
33
- # Join the namespaces from a list of settings to create a path prefix.
34
- # @param settings [Array] list of Grape::Util::InheritableSettings.
26
+ def ==(other)
27
+ other.class == self.class &&
28
+ other.space == space &&
29
+ other.requirements == requirements &&
30
+ other.options == options
31
+ end
32
+ alias eql? ==
33
+
34
+ def hash
35
+ [self.class, space, requirements, options].hash
36
+ end
37
+
38
+ # Join the namespaces from a list of Namespace objects to create a path
39
+ # prefix.
40
+ # @param settings [Array] list of Grape::Namespace objects.
35
41
  def self.joined_space_path(settings)
36
- Grape::Router.normalize_path(JoinedSpaceCache[joined_space(settings)])
42
+ JoinedSpaceCache[joined_space(settings)]
37
43
  end
38
44
 
39
45
  class JoinedSpaceCache < Grape::Util::Cache
40
46
  def initialize
47
+ super
41
48
  @cache = Hash.new do |h, joined_space|
42
- h[joined_space] = -joined_space.join('/')
49
+ h[joined_space] = Grape::Util::PathNormalizer.call(joined_space.join('/'))
43
50
  end
44
51
  end
45
52
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class Base
6
+ class << self
7
+ def call(_params)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ private
12
+
13
+ def inherited(klass)
14
+ super
15
+ ParamsBuilder.register(klass)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class Hash < Base
6
+ def self.call(params)
7
+ params.deep_symbolize_keys
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class HashWithIndifferentAccess < Base
6
+ def self.call(params)
7
+ ActiveSupport::HashWithIndifferentAccess.new(params)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class HashieMash < Base
6
+ def self.call(params)
7
+ ::Hashie::Mash.new(params)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ extend Grape::Util::Registry
6
+
7
+ module_function
8
+
9
+ def params_builder_for(short_name)
10
+ raise Grape::Exceptions::UnknownParamsBuilder, short_name unless registry.key?(short_name)
11
+
12
+ registry[short_name]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Parser
5
+ class Base
6
+ def self.call(_object, _env)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def self.inherited(klass)
11
+ super
12
+ Parser.register(klass)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,14 +2,12 @@
2
2
 
3
3
  module Grape
4
4
  module Parser
5
- module Json
6
- class << self
7
- def call(object, _env)
8
- ::Grape::Json.load(object)
9
- rescue ::Grape::Json::ParseError
10
- # handle JSON parsing errors via the rescue handlers or provide error message
11
- raise Grape::Exceptions::InvalidMessageBody.new('application/json')
12
- end
5
+ class Json < Base
6
+ def self.call(object, _env)
7
+ ::Grape::Json.parse(object)
8
+ rescue ::Grape::Json::ParseError
9
+ # handle JSON parsing errors via the rescue handlers or provide error message
10
+ raise Grape::Exceptions::InvalidMessageBody.new('application/json')
13
11
  end
14
12
  end
15
13
  end
@@ -2,14 +2,12 @@
2
2
 
3
3
  module Grape
4
4
  module Parser
5
- module Xml
6
- class << self
7
- def call(object, _env)
8
- ::Grape::Xml.parse(object)
9
- rescue ::Grape::Xml::ParseError
10
- # handle XML parsing errors via the rescue handlers or provide error message
11
- raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
12
- end
5
+ class Xml < Base
6
+ def self.call(object, _env)
7
+ ::Grape::Xml.parse(object)
8
+ rescue ::Grape::Xml::ParseError
9
+ # handle XML parsing errors via the rescue handlers or provide error message
10
+ raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
13
11
  end
14
12
  end
15
13
  end