grape 1.8.0 → 3.3.5

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 (312) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +391 -1
  3. data/CONTRIBUTING.md +3 -11
  4. data/README.md +648 -552
  5. data/UPGRADING.md +803 -7
  6. data/grape.gemspec +11 -14
  7. data/lib/grape/api/instance.rb +68 -158
  8. data/lib/grape/api.rb +71 -107
  9. data/lib/grape/content_types.rb +23 -11
  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 +14 -58
  14. data/lib/grape/dsl/declared.rb +35 -0
  15. data/lib/grape/dsl/desc.rb +15 -63
  16. data/lib/grape/dsl/entity.rb +88 -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 +56 -295
  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 +50 -82
  23. data/lib/grape/dsl/request_response.rb +144 -146
  24. data/lib/grape/dsl/rescue_options.rb +24 -0
  25. data/lib/grape/dsl/routing.rb +261 -186
  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 +19 -0
  30. data/lib/grape/endpoint.rb +277 -250
  31. data/lib/grape/{util/env.rb → env.rb} +1 -8
  32. data/lib/grape/error_formatter/base.rb +56 -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 +5 -25
  38. data/lib/grape/exceptions/base.rb +22 -58
  39. data/lib/grape/exceptions/error_response.rb +45 -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_parameter.rb +1 -1
  53. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  54. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  55. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  56. data/lib/grape/exceptions/validation.rb +25 -9
  57. data/lib/grape/exceptions/validation_array_errors.rb +5 -0
  58. data/lib/grape/exceptions/validation_errors.rb +22 -26
  59. data/lib/grape/formatter/base.rb +16 -0
  60. data/lib/grape/formatter/json.rb +4 -6
  61. data/lib/grape/formatter/serializable_hash.rb +6 -10
  62. data/lib/grape/formatter/txt.rb +3 -5
  63. data/lib/grape/formatter/xml.rb +4 -6
  64. data/lib/grape/formatter.rb +7 -25
  65. data/lib/grape/json.rb +46 -0
  66. data/lib/grape/locale/en.yml +43 -42
  67. data/lib/grape/middleware/auth/base.rb +11 -33
  68. data/lib/grape/middleware/auth/dsl.rb +37 -31
  69. data/lib/grape/middleware/auth/strategies.rb +1 -2
  70. data/lib/grape/middleware/base.rb +56 -33
  71. data/lib/grape/middleware/deprecated_options_hash_access.rb +19 -0
  72. data/lib/grape/middleware/error.rb +183 -91
  73. data/lib/grape/middleware/formatter.rb +94 -105
  74. data/lib/grape/middleware/precomputed_content_types.rb +46 -0
  75. data/lib/grape/middleware/stack.rb +27 -38
  76. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  77. data/lib/grape/middleware/versioner/base.rb +72 -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 +13 -32
  81. data/lib/grape/middleware/versioner.rb +7 -14
  82. data/lib/grape/mountable.rb +22 -0
  83. data/lib/grape/namespace.rb +18 -12
  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 +38 -63
  94. data/lib/grape/railtie.rb +9 -0
  95. data/lib/grape/request.rb +168 -26
  96. data/lib/grape/router/base_route.rb +48 -0
  97. data/lib/grape/router/greedy_route.rb +26 -0
  98. data/lib/grape/router/mustermann_pattern.rb +44 -0
  99. data/lib/grape/router/pattern.rb +30 -34
  100. data/lib/grape/router/route.rb +28 -61
  101. data/lib/grape/router.rb +71 -86
  102. data/lib/grape/serve_stream/file_body.rb +1 -0
  103. data/lib/grape/serve_stream/sendfile_response.rb +3 -5
  104. data/lib/grape/serve_stream/stream_response.rb +1 -0
  105. data/lib/grape/testing.rb +33 -0
  106. data/lib/grape/util/api_description.rb +58 -0
  107. data/lib/grape/util/base_inheritable.rb +14 -14
  108. data/lib/grape/util/cache.rb +1 -3
  109. data/lib/grape/util/deep_freeze.rb +35 -0
  110. data/lib/grape/util/endpoint_configuration.rb +1 -1
  111. data/lib/grape/util/header.rb +13 -0
  112. data/lib/grape/util/inheritable_setting.rb +51 -27
  113. data/lib/grape/util/inheritable_values.rb +7 -5
  114. data/lib/grape/util/lazy/base.rb +16 -0
  115. data/lib/grape/util/lazy/block.rb +22 -0
  116. data/lib/grape/util/lazy/value.rb +31 -0
  117. data/lib/grape/util/lazy/value_array.rb +21 -0
  118. data/lib/grape/util/lazy/value_enumerable.rb +31 -0
  119. data/lib/grape/util/lazy/value_hash.rb +21 -0
  120. data/lib/grape/util/media_type.rb +67 -0
  121. data/lib/grape/util/path_normalizer.rb +34 -0
  122. data/lib/grape/util/registry.rb +27 -0
  123. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  124. data/lib/grape/util/stackable_values.rb +12 -14
  125. data/lib/grape/util/translation.rb +42 -0
  126. data/lib/grape/validations/attributes_iterator.rb +43 -27
  127. data/lib/grape/validations/coerce_options.rb +21 -0
  128. data/lib/grape/validations/contract_scope.rb +28 -0
  129. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  130. data/lib/grape/validations/oneof_collector.rb +39 -0
  131. data/lib/grape/validations/param_scope_tracker.rb +62 -0
  132. data/lib/grape/validations/params_documentation.rb +52 -0
  133. data/lib/grape/validations/params_scope.rb +180 -303
  134. data/lib/grape/validations/shared_options.rb +19 -0
  135. data/lib/grape/validations/single_attribute_iterator.rb +2 -2
  136. data/lib/grape/validations/types/array_coercer.rb +4 -7
  137. data/lib/grape/validations/types/custom_type_coercer.rb +41 -85
  138. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  139. data/lib/grape/validations/types/dry_type_coercer.rb +15 -28
  140. data/lib/grape/validations/types/json.rb +0 -2
  141. data/lib/grape/validations/types/primitive_coercer.rb +11 -35
  142. data/lib/grape/validations/types/set_coercer.rb +1 -4
  143. data/lib/grape/validations/types/variant_collection_coercer.rb +11 -0
  144. data/lib/grape/validations/types.rb +29 -54
  145. data/lib/grape/validations/validations_spec.rb +164 -0
  146. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  147. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  148. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  149. data/lib/grape/validations/validators/base.rb +120 -36
  150. data/lib/grape/validations/validators/coerce_validator.rb +18 -36
  151. data/lib/grape/validations/validators/contract_scope_validator.rb +45 -0
  152. data/lib/grape/validations/validators/default_validator.rb +13 -16
  153. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  154. data/lib/grape/validations/validators/except_values_validator.rb +15 -5
  155. data/lib/grape/validations/validators/length_validator.rb +48 -0
  156. data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
  157. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  158. data/lib/grape/validations/validators/oneof_validator.rb +49 -0
  159. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  160. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  161. data/lib/grape/validations/validators/same_as_validator.rb +6 -15
  162. data/lib/grape/validations/validators/values_validator.rb +36 -65
  163. data/lib/grape/validations.rb +8 -21
  164. data/lib/grape/version.rb +1 -2
  165. data/lib/grape/xml.rb +17 -0
  166. data/lib/grape.rb +75 -288
  167. metadata +79 -291
  168. data/lib/grape/api/helpers.rb +0 -9
  169. data/lib/grape/dsl/api.rb +0 -19
  170. data/lib/grape/dsl/configuration.rb +0 -15
  171. data/lib/grape/eager_load.rb +0 -20
  172. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  173. data/lib/grape/exceptions/missing_option.rb +0 -11
  174. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  175. data/lib/grape/exceptions/unknown_options.rb +0 -11
  176. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
  177. data/lib/grape/extensions/hash.rb +0 -22
  178. data/lib/grape/extensions/hashie/mash.rb +0 -26
  179. data/lib/grape/http/headers.rb +0 -61
  180. data/lib/grape/middleware/globals.rb +0 -16
  181. data/lib/grape/middleware/helpers.rb +0 -12
  182. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  183. data/lib/grape/router/attribute_translator.rb +0 -63
  184. data/lib/grape/types/invalid_value.rb +0 -8
  185. data/lib/grape/util/json.rb +0 -12
  186. data/lib/grape/util/lazy_block.rb +0 -27
  187. data/lib/grape/util/lazy_object.rb +0 -43
  188. data/lib/grape/util/lazy_value.rb +0 -91
  189. data/lib/grape/util/registrable.rb +0 -15
  190. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  191. data/lib/grape/util/xml.rb +0 -10
  192. data/lib/grape/validations/attributes_doc.rb +0 -58
  193. data/lib/grape/validations/types/build_coercer.rb +0 -94
  194. data/lib/grape/validations/validator_factory.rb +0 -15
  195. data/spec/grape/api/custom_validations_spec.rb +0 -213
  196. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  197. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  198. data/spec/grape/api/documentation_spec.rb +0 -59
  199. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  200. data/spec/grape/api/instance_spec.rb +0 -103
  201. data/spec/grape/api/invalid_format_spec.rb +0 -45
  202. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  203. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  204. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  205. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  206. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  207. data/spec/grape/api/recognize_path_spec.rb +0 -21
  208. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  209. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  210. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  211. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  212. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  213. data/spec/grape/api_remount_spec.rb +0 -509
  214. data/spec/grape/api_spec.rb +0 -4356
  215. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  216. data/spec/grape/dsl/desc_spec.rb +0 -98
  217. data/spec/grape/dsl/headers_spec.rb +0 -62
  218. data/spec/grape/dsl/helpers_spec.rb +0 -100
  219. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  220. data/spec/grape/dsl/logger_spec.rb +0 -24
  221. data/spec/grape/dsl/middleware_spec.rb +0 -60
  222. data/spec/grape/dsl/parameters_spec.rb +0 -180
  223. data/spec/grape/dsl/request_response_spec.rb +0 -225
  224. data/spec/grape/dsl/routing_spec.rb +0 -275
  225. data/spec/grape/dsl/settings_spec.rb +0 -261
  226. data/spec/grape/dsl/validations_spec.rb +0 -55
  227. data/spec/grape/endpoint/declared_spec.rb +0 -846
  228. data/spec/grape/endpoint_spec.rb +0 -1085
  229. data/spec/grape/entity_spec.rb +0 -336
  230. data/spec/grape/exceptions/base_spec.rb +0 -81
  231. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  232. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  233. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  234. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  235. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  236. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  237. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  238. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  239. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  240. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  241. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  242. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  243. data/spec/grape/exceptions/validation_spec.rb +0 -19
  244. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  245. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  246. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  247. data/spec/grape/grape_spec.rb +0 -9
  248. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  249. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  250. data/spec/grape/integration/rack_spec.rb +0 -51
  251. data/spec/grape/loading_spec.rb +0 -44
  252. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  253. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  254. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  255. data/spec/grape/middleware/base_spec.rb +0 -221
  256. data/spec/grape/middleware/error_spec.rb +0 -85
  257. data/spec/grape/middleware/exception_spec.rb +0 -294
  258. data/spec/grape/middleware/formatter_spec.rb +0 -461
  259. data/spec/grape/middleware/globals_spec.rb +0 -30
  260. data/spec/grape/middleware/stack_spec.rb +0 -155
  261. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  262. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  263. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  264. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  265. data/spec/grape/middleware/versioner_spec.rb +0 -21
  266. data/spec/grape/named_api_spec.rb +0 -19
  267. data/spec/grape/parser_spec.rb +0 -86
  268. data/spec/grape/path_spec.rb +0 -252
  269. data/spec/grape/presenters/presenter_spec.rb +0 -71
  270. data/spec/grape/request_spec.rb +0 -126
  271. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  272. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  273. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  274. data/spec/grape/util/stackable_values_spec.rb +0 -128
  275. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  276. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  277. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  278. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  279. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  280. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  281. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  282. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  283. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  284. data/spec/grape/validations/types_spec.rb +0 -111
  285. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  286. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  287. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  288. data/spec/grape/validations/validators/base_spec.rb +0 -38
  289. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  290. data/spec/grape/validations/validators/default_spec.rb +0 -463
  291. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  292. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  293. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  294. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  295. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  296. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  297. data/spec/grape/validations/validators/values_spec.rb +0 -733
  298. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  299. data/spec/grape/validations_spec.rb +0 -2030
  300. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  301. data/spec/integration/multi_json/json_spec.rb +0 -7
  302. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  303. data/spec/shared/deprecated_class_examples.rb +0 -16
  304. data/spec/shared/versioning_examples.rb +0 -215
  305. data/spec/spec_helper.rb +0 -52
  306. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  307. data/spec/support/chunks.rb +0 -14
  308. data/spec/support/content_type_helpers.rb +0 -15
  309. data/spec/support/endpoint_faker.rb +0 -25
  310. data/spec/support/file_streamer.rb +0 -13
  311. data/spec/support/integer_helpers.rb +0 -13
  312. data/spec/support/versioned_helpers.rb +0 -55
@@ -5,10 +5,12 @@ module Grape
5
5
  # Class to handle the stack of middlewares based on ActionDispatch::MiddlewareStack
6
6
  # It allows to insert and insert after
7
7
  class Stack
8
+ extend Forwardable
9
+
8
10
  class Middleware
9
11
  attr_reader :args, :block, :klass
10
12
 
11
- def initialize(klass, *args, &block)
13
+ def initialize(klass, args, block)
12
14
  @klass = klass
13
15
  @args = args
14
16
  @block = block
@@ -26,13 +28,18 @@ module Grape
26
28
  klass == other || (name.nil? && klass.superclass == other)
27
29
  end
28
30
  end
31
+ alias eql? ==
29
32
 
30
33
  def inspect
31
34
  klass.to_s
32
35
  end
33
36
 
34
- def use_in(builder)
35
- builder.use(@klass, *@args, &@block)
37
+ def build(builder)
38
+ # we need to force the ruby2_keywords_hash for middlewares that initialize contains keywords
39
+ # like ActionDispatch::RequestId since middleware arguments are serialized
40
+ # https://rubyapi.org/3.4/o/hash#method-c-ruby2_keywords_hash
41
+ args[-1] = Hash.ruby2_keywords_hash(args[-1]) if args.last.is_a?(Hash) && Hash.respond_to?(:ruby2_keywords_hash)
42
+ builder.use(klass, *args, &block)
36
43
  end
37
44
  end
38
45
 
@@ -40,73 +47,55 @@ module Grape
40
47
 
41
48
  attr_accessor :middlewares, :others
42
49
 
50
+ def_delegators :middlewares, :each, :size, :last, :[]
51
+
43
52
  def initialize
44
53
  @middlewares = []
45
54
  @others = []
46
55
  end
47
56
 
48
- def each(&block)
49
- @middlewares.each(&block)
50
- end
51
-
52
- def size
53
- middlewares.size
54
- end
55
-
56
- def last
57
- middlewares.last
58
- end
59
-
60
- def [](i)
61
- middlewares[i]
62
- end
63
-
64
- def insert(index, *args, &block)
57
+ def insert(index, klass, *args, &block)
65
58
  index = assert_index(index, :before)
66
- middleware = self.class::Middleware.new(*args, &block)
67
- middlewares.insert(index, middleware)
59
+ middlewares.insert(index, self.class::Middleware.new(klass, args, block))
68
60
  end
69
- ruby2_keywords :insert if respond_to?(:ruby2_keywords, true)
70
61
 
71
62
  alias insert_before insert
72
63
 
73
- def insert_after(index, *args, &block)
64
+ def insert_after(index, ...)
74
65
  index = assert_index(index, :after)
75
- insert(index + 1, *args, &block)
66
+ insert(index + 1, ...)
76
67
  end
77
- ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true)
78
68
 
79
- def use(*args, &block)
80
- middleware = self.class::Middleware.new(*args, &block)
69
+ def use(klass, *args, &block)
70
+ middleware = self.class::Middleware.new(klass, args, block)
81
71
  middlewares.push(middleware)
82
72
  end
83
- ruby2_keywords :use if respond_to?(:ruby2_keywords, true)
84
73
 
85
74
  def merge_with(middleware_specs)
86
- middleware_specs.each do |operation, *args|
75
+ middleware_specs.each do |operation, klass, *args|
87
76
  if args.last.is_a?(Proc)
88
77
  last_proc = args.pop
89
- public_send(operation, *args, &last_proc)
78
+ public_send(operation, klass, *args, &last_proc)
90
79
  else
91
- public_send(operation, *args)
80
+ public_send(operation, klass, *args)
92
81
  end
93
82
  end
94
83
  end
95
84
 
96
85
  # @return [Rack::Builder] the builder object with our middlewares applied
97
- def build(builder = Rack::Builder.new)
86
+ def build
87
+ builder = Rack::Builder.new
98
88
  others.shift(others.size).each { |m| merge_with(m) }
99
- middlewares.each do |m|
100
- m.use_in(builder)
101
- end
89
+ middlewares.each { |m| m.build(builder) }
102
90
  builder
103
91
  end
104
92
 
105
93
  # @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later
106
94
  # @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
107
95
  def concat(other_specs)
108
- @others << Array(other_specs).reject { |o| o.first == :use }
109
- merge_with(Array(other_specs).select { |o| o.first == :use })
96
+ use, not_use = other_specs.partition { |o| o.first == :use }
97
+ others << not_use
98
+ merge_with(use)
110
99
  end
111
100
 
112
101
  protected
@@ -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
@@ -20,44 +18,19 @@ module Grape
20
18
  # route.
21
19
  class AcceptVersionHeader < Base
22
20
  def before
23
- potential_version = (env[Grape::Http::Headers::HTTP_ACCEPT_VERSION] || '').strip
24
-
25
- if strict? && potential_version.empty?
26
- # If no Accept-Version header:
27
- throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
28
- end
29
-
30
- return if potential_version.empty?
21
+ potential_version = try_scrub(env['HTTP_ACCEPT_VERSION'])
22
+ not_acceptable!('Accept-Version header must be set.') if strict && potential_version.blank?
31
23
 
32
- # If the requested version is not supported:
33
- throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.' unless versions.any? { |v| v.to_s == potential_version }
24
+ return if potential_version.blank?
34
25
 
26
+ not_acceptable!('The requested version is not supported.') unless potential_version_match?(potential_version)
35
27
  env[Grape::Env::API_VERSION] = potential_version
36
28
  end
37
29
 
38
30
  private
39
31
 
40
- def versions
41
- options[:versions] || []
42
- end
43
-
44
- def strict?
45
- options[:version_options] && options[:version_options][:strict]
46
- end
47
-
48
- # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
49
- # of routes (see Grape::Router) for more information). To prevent
50
- # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
51
- def cascade?
52
- if options[:version_options]&.key?(:cascade)
53
- options[:version_options][:cascade]
54
- else
55
- true
56
- end
57
- end
58
-
59
- def error_headers
60
- cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
32
+ def not_acceptable!(message)
33
+ throw :error, Grape::Exceptions::ErrorResponse.new(status: 406, headers: error_headers, message:)
61
34
  end
62
35
  end
63
36
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Middleware
5
+ module Versioner
6
+ class Base < Grape::Middleware::Base
7
+ extend Forwardable
8
+ include Grape::Middleware::PrecomputedContentTypes
9
+
10
+ Options = Data.define(
11
+ :content_types, :format, :mount_path, :pattern, :prefix, :version_options, :versions
12
+ ) do
13
+ include Grape::Middleware::DeprecatedOptionsHashAccess
14
+
15
+ def initialize(
16
+ content_types: nil, format: nil, mount_path: nil, pattern: /.*/i, prefix: nil,
17
+ version_options: Grape::DSL::VersionOptions.new, versions: nil
18
+ )
19
+ super
20
+ end
21
+ end
22
+
23
+ # @deprecated Kept as a frozen Hash representation of the {Options}
24
+ # defaults for back-compat. Will be removed in a future release.
25
+ DEFAULT_OPTIONS = Options.new.to_h.freeze
26
+
27
+ CASCADE_PASS_HEADER = { 'X-Cascade' => 'pass' }.freeze
28
+
29
+ def self.inherited(klass)
30
+ super
31
+ Versioner.register(klass)
32
+ end
33
+
34
+ attr_reader :available_media_types, :error_headers, :versions
35
+
36
+ def_delegators :config, :mount_path, :pattern, :prefix, :version_options
37
+ def_delegators :version_options, :cascade, :parameter, :strict, :vendor
38
+
39
+ def initialize(app, **options)
40
+ super
41
+ @versions = config.versions&.map(&:to_s) # making sure versions are strings to ease potential match
42
+ @error_headers = cascade ? CASCADE_PASS_HEADER : {}
43
+ @available_media_types = build_available_media_types
44
+ end
45
+
46
+ def potential_version_match?(potential_version)
47
+ versions.blank? || versions.include?(potential_version)
48
+ end
49
+
50
+ def version_not_found!
51
+ throw :error, Grape::Exceptions::ErrorResponse.new(status: 404, message: '404 API Version Not Found', headers: CASCADE_PASS_HEADER)
52
+ end
53
+
54
+ private
55
+
56
+ def build_available_media_types
57
+ media_types = []
58
+ base_media_type = "application/vnd.#{vendor}"
59
+ versions&.reverse_each do |version|
60
+ versioned_base = "#{base_media_type}-#{version}"
61
+ content_types.each_key { |extension| media_types << "#{versioned_base}+#{extension}" }
62
+ media_types << versioned_base
63
+ end
64
+ content_types.each_key { |extension| media_types << "#{base_media_type}+#{extension}" }
65
+ media_types << base_media_type
66
+ media_types.concat(content_types.values.flatten)
67
+ media_types
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -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,27 @@ 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
+ path_info = Grape::Util::PathNormalizer.call(env[Rack::PATH_INFO])
27
+ return if path_info == '/'
31
28
 
32
- if prefix && path.index(prefix) == 0 # rubocop:disable all
33
- path.sub!(prefix, '')
34
- path = Grape::Router.normalize_path(path)
29
+ path_info = @prefixes.reduce(path_info) do |pi, path|
30
+ pi.start_with?(path) ? pi.delete_prefix(path) : pi
35
31
  end
36
32
 
37
- pieces = path.split('/')
38
- potential_version = pieces[1]
39
- return unless potential_version&.match?(options[:pattern])
40
-
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
43
- end
44
-
45
- private
33
+ slash_position = path_info.index('/', 1) # omit the first one
34
+ return unless slash_position
46
35
 
47
- def mounted_path?(path)
48
- return false unless mount_path && path.start_with?(mount_path)
36
+ potential_version = path_info[1..(slash_position - 1)]
37
+ return unless potential_version.match?(pattern)
49
38
 
50
- rest = path.slice(mount_path.length..-1)
51
- rest.start_with?('/') || rest.empty?
52
- end
53
-
54
- def mount_path
55
- @mount_path ||= options[:mount_path] && options[:mount_path] != '/' ? options[:mount_path] : ''
56
- end
57
-
58
- def prefix
59
- Grape::Router.normalize_path(options[:prefix].to_s) if options[:prefix]
39
+ version_not_found! unless potential_version_match?(potential_version)
40
+ env[Grape::Env::API_VERSION] = potential_version
60
41
  end
61
42
  end
62
43
  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