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
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module DSL
5
+ # Immutable value object holding the response-shaping booleans accepted
6
+ # by +Grape::DSL::RequestResponse#rescue_from+. Recorded on the
7
+ # inheritable settings via +Grape::Util::InheritableSetting#add_rescue_options+
8
+ # (the nearest scope's latest registration wins on read, see
9
+ # +#rescue_options+) and delegated to by +Grape::Middleware::Error+ (which forwards
10
+ # +backtrace+/+original_exception+ to the formatter as
11
+ # +include_backtrace+/+include_original_exception+).
12
+ #
13
+ # Defaults are duplicated on +#initialize+ here and on +#rescue_from+'s
14
+ # signature on purpose: keeping them on both sides means each entry point
15
+ # is self-documenting without needing to import a shared constant — the
16
+ # DSL signature shows what a user sees in the IDE, and the Data object
17
+ # has working defaults when constructed directly (spec fixtures, a
18
+ # middleware built by hand). The two must stay in lockstep.
19
+ RescueOptions = Data.define(:backtrace, :original_exception) do
20
+ def initialize(backtrace: false, original_exception: false)
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,228 +3,370 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Routing
6
- extend ActiveSupport::Concern
7
- include Grape::DSL::Configuration
8
-
9
- module ClassMethods
10
- attr_reader :endpoints
11
-
12
- # Specify an API version.
13
- #
14
- # @example API with legacy support.
15
- # class MyAPI < Grape::API
16
- # version 'v2'
17
- #
18
- # get '/main' do
19
- # {some: 'data'}
20
- # end
21
- #
22
- # version 'v1' do
23
- # get '/main' do
24
- # {legacy: 'data'}
25
- # end
26
- # end
27
- # end
28
- #
29
- def version(*args, &block)
30
- if args.any?
31
- options = args.extract_options!
32
- options = options.reverse_merge(using: :path)
33
- requested_versions = args.flatten
34
-
35
- raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
36
-
37
- @versions = versions | requested_versions
38
-
39
- if block
40
- within_namespace do
41
- namespace_inheritable(:version, requested_versions)
42
- namespace_inheritable(:version_options, options)
43
-
44
- instance_eval(&block)
45
- end
46
- else
47
- namespace_inheritable(:version, requested_versions)
48
- namespace_inheritable(:version_options, options)
49
- end
50
- end
6
+ attr_reader :endpoints
51
7
 
52
- @versions.last if instance_variable_defined?(:@versions) && @versions
53
- end
8
+ def given(conditional_option, &)
9
+ return unless conditional_option
54
10
 
55
- # Define a root URL prefix for your entire API.
56
- def prefix(prefix = nil)
57
- namespace_inheritable(:root_prefix, prefix)
58
- end
11
+ mounted(&)
12
+ end
13
+
14
+ def mounted(&block)
15
+ evaluate_as_instance_with_configuration(block, lazy: true)
16
+ end
17
+
18
+ def cascade(value = nil)
19
+ return inheritable_setting.cascade_defined? ? inheritable_setting.cascade : true if value.nil?
20
+
21
+ inheritable_setting.cascade = value
22
+ end
59
23
 
60
- # Create a scope without affecting the URL.
61
- #
62
- # @param _name [Symbol] Purely placebo, just allows to name the scope to
63
- # make the code more readable.
64
- def scope(_name = nil, &block)
24
+ # Specify an API version.
25
+ #
26
+ # Called without arguments, returns the most recently declared version
27
+ # (or +nil+). Called with one or more version strings, registers them
28
+ # and stores a {Grape::DSL::VersionOptions} value object on the
29
+ # inheritable settings; when given a block, the registration applies
30
+ # within a nested namespace.
31
+ #
32
+ # @example API with legacy support.
33
+ # class MyAPI < Grape::API
34
+ # version 'v2'
35
+ #
36
+ # get '/main' do
37
+ # {some: 'data'}
38
+ # end
39
+ #
40
+ # version 'v1' do
41
+ # get '/main' do
42
+ # {legacy: 'data'}
43
+ # end
44
+ # end
45
+ # end
46
+ #
47
+ # @param args [Array<String, Symbol>] one or more version identifiers.
48
+ # @param using [Symbol] versioning strategy — one of +:path+ (default),
49
+ # +:header+, +:param+, or +:accept_version_header+.
50
+ # @param cascade [Boolean] forward to subsequent routes via the
51
+ # +X-Cascade+ header on version mismatch. Defaults to +true+.
52
+ # @param parameter [String] name of the query/body parameter that
53
+ # carries the version when +using: :param+. Defaults to +'apiver'+.
54
+ # @param strict [Boolean] reject requests that don't supply a usable
55
+ # version (header strategies). Defaults to +false+.
56
+ # @param vendor [String, nil] vendor segment for the +:header+
57
+ # strategy (+application/vnd.<vendor>-<version>+); required when
58
+ # +using: :header+.
59
+ # @yield optional block to scope routes under this version.
60
+ # @return [String, nil] the most recently declared version.
61
+ # @raise [Grape::Exceptions::MissingVendorOption] when +using: :header+
62
+ # is supplied without a +:vendor+.
63
+ def version(*args, using: :path, cascade: true, parameter: 'apiver', strict: false, vendor: nil, &block)
64
+ return @versions&.last if args.empty?
65
+
66
+ raise Grape::Exceptions::MissingVendorOption.new if using == :header && vendor.nil?
67
+
68
+ requested_versions = args.flatten.map(&:to_s)
69
+ options = VersionOptions.new(using:, cascade:, parameter:, strict:, vendor:)
70
+
71
+ @versions = versions | requested_versions
72
+
73
+ if block
65
74
  within_namespace do
66
- nest(block)
75
+ inheritable_setting.version = requested_versions
76
+ inheritable_setting.version_options = options
77
+ instance_eval(&block)
67
78
  end
79
+ else
80
+ inheritable_setting.version = requested_versions
81
+ inheritable_setting.version_options = options
68
82
  end
69
83
 
70
- # Do not route HEAD requests to GET requests automatically.
71
- def do_not_route_head!
72
- namespace_inheritable(:do_not_route_head, true)
73
- end
84
+ @versions&.last
85
+ end
74
86
 
75
- # Do not automatically route OPTIONS.
76
- def do_not_route_options!
77
- namespace_inheritable(:do_not_route_options, true)
78
- end
87
+ # Define a root URL prefix for your entire API.
88
+ def prefix(prefix = nil)
89
+ return inheritable_setting.root_prefix if prefix.nil?
79
90
 
80
- def do_not_document!
81
- namespace_inheritable(:do_not_document, true)
82
- end
91
+ inheritable_setting.root_prefix = prefix.to_s
92
+ end
83
93
 
84
- def mount(mounts, *opts)
85
- mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
86
- mounts.each_pair do |app, path|
87
- if app.respond_to?(:mount_instance)
88
- opts_with = opts.any? ? opts.shift[:with] : {}
89
- mount({ app.mount_instance(configuration: opts_with) => path })
90
- next
91
- end
92
- in_setting = inheritable_setting
93
-
94
- if app.respond_to?(:inheritable_setting, true)
95
- mount_path = Grape::Router.normalize_path(path)
96
- app.top_level_setting.namespace_stackable[:mount_path] = mount_path
97
-
98
- app.inherit_settings(inheritable_setting)
99
-
100
- in_setting = app.top_level_setting
101
-
102
- app.change!
103
- change!
104
- end
105
-
106
- endpoints << Grape::Endpoint.new(
107
- in_setting,
108
- method: :any,
109
- path: path,
110
- app: app,
111
- route_options: { anchor: false },
112
- forward_match: !app.respond_to?(:inheritable_setting),
113
- for: self
114
- )
115
- end
94
+ # Create a scope without affecting the URL.
95
+ #
96
+ # @param _name [Symbol] Purely placebo, just allows to name the scope to
97
+ # make the code more readable.
98
+ def scope(_name = nil, &block)
99
+ within_namespace do
100
+ nest(block)
116
101
  end
102
+ end
117
103
 
118
- # Defines a route that will be recognized
119
- # by the Grape API.
120
- #
121
- # @param methods [HTTP Verb] One or more HTTP verbs that are accepted by this route. Set to `:any` if you want any verb to be accepted.
122
- # @param paths [String] One or more strings representing the URL segment(s) for this route.
123
- #
124
- # @example Defining a basic route.
125
- # class MyAPI < Grape::API
126
- # route(:any, '/hello') do
127
- # {hello: 'world'}
128
- # end
129
- # end
130
- def route(methods, paths = ['/'], route_options = {}, &block)
131
- methods = '*' if methods == :any
132
- endpoint_options = {
133
- method: methods,
134
- path: paths,
135
- for: self,
136
- route_options: {
137
- params: namespace_stackable_with_hash(:params) || {}
138
- }.deep_merge(route_setting(:description) || {}).deep_merge(route_options || {})
139
- }
140
-
141
- new_endpoint = Grape::Endpoint.new(inheritable_setting, endpoint_options, &block)
142
- endpoints << new_endpoint unless endpoints.any? { |e| e.equals?(new_endpoint) }
143
-
144
- route_end
145
- reset_validations!
104
+ def build_with(build_with)
105
+ inheritable_setting.build_params_with = build_with
106
+ end
107
+
108
+ # Do not route HEAD requests to GET requests automatically.
109
+ def do_not_route_head!
110
+ inheritable_setting.do_not_route_head!
111
+ end
112
+
113
+ # Do not automatically route OPTIONS.
114
+ def do_not_route_options!
115
+ inheritable_setting.do_not_route_options!
116
+ end
117
+
118
+ def lint!
119
+ inheritable_setting.lint!
120
+ end
121
+
122
+ def do_not_document!
123
+ inheritable_setting.do_not_document!
124
+ end
125
+
126
+ def mount(mounts, opts = {})
127
+ mount_opts = opts
128
+ if opts[:refresh_already_mounted]
129
+ Grape.deprecator.warn('`refresh_already_mounted` is not a `mount` option and will be ignored in a future release.')
130
+ drop_endpoints_mounted_for(mounts)
131
+ # Dropped before the recursion below re-enters with the same options,
132
+ # so a Grape API does not warn once per mount and once per instance.
133
+ mount_opts = opts.except(:refresh_already_mounted)
146
134
  end
147
135
 
148
- Grape::Http::Headers::SUPPORTED_METHODS.each do |supported_method|
149
- define_method supported_method.downcase do |*args, &block|
150
- options = args.extract_options!
151
- paths = args.first || ['/']
152
- route(supported_method, paths, options, &block)
136
+ normalize_mounts(mounts).each_pair do |app, path|
137
+ if app.respond_to?(:mount_instance)
138
+ mount({ app.mount_instance(configuration: mount_opts[:with] || {}) => path }, mount_opts)
139
+ next
153
140
  end
154
- end
141
+ in_setting = inheritable_setting
142
+
143
+ # Past the mount_instance branch above, a Grape app here is an already
144
+ # instantiated Grape::API::Instance (vs. a bare Rack app).
145
+ if app.is_a?(Grape::Mountable)
146
+ mount_path = Grape::Util::PathNormalizer.call(path)
147
+ app.top_level_setting.add_mount_path(mount_path)
148
+
149
+ app.inherit_settings(inheritable_setting)
155
150
 
156
- # Declare a "namespace", which prefixes all subordinate routes with its
157
- # name. Any endpoints within a namespace, group, resource or segment,
158
- # etc., will share their parent context as well as any configuration
159
- # done in the namespace context.
160
- #
161
- # @example
162
- #
163
- # namespace :foo do
164
- # get 'bar' do
165
- # # defines the endpoint: GET /foo/bar
166
- # end
167
- # end
168
- def namespace(space = nil, options = {}, &block)
169
- @namespace_description = nil unless instance_variable_defined?(:@namespace_description) && @namespace_description
170
-
171
- if space || block
172
- within_namespace do
173
- previous_namespace_description = @namespace_description
174
- @namespace_description = (@namespace_description || {}).deep_merge(namespace_setting(:description) || {})
175
- nest(block) do
176
- namespace_stackable(:namespace, Namespace.new(space, **options)) if space
177
- end
178
- @namespace_description = previous_namespace_description
179
- end
180
- else
181
- Namespace.joined_space_path(namespace_stackable(:namespace))
151
+ in_setting = app.top_level_setting
152
+
153
+ app.change!
154
+ change!
182
155
  end
156
+
157
+ endpoints << Grape::Endpoint.new(
158
+ in_setting,
159
+ http_methods: :any,
160
+ path:,
161
+ app:,
162
+ anchor: false,
163
+ api: self
164
+ )
183
165
  end
166
+ end
184
167
 
185
- alias group namespace
186
- alias resource namespace
187
- alias resources namespace
188
- alias segment namespace
168
+ # Defines a route that will be recognized
169
+ # by the Grape API.
170
+ #
171
+ # @param methods [HTTP Verb] One or more HTTP verbs that are accepted by this route. Set to `:any` if you want any verb to be accepted.
172
+ # @param paths [String] One or more strings representing the URL segment(s) for this route.
173
+ # @param requirements [Hash] Regular-expression constraints for named path params; the route matches only when every requirement is satisfied.
174
+ # @param anchor [Boolean] Whether the route is anchored to the whole path. Defaults to `true`; pass `false` for catch-all routes (e.g. `'/(*:path)'`).
175
+ # @param route_options [Hash] Any additional custom options, carried through to `route.options`.
176
+ #
177
+ # @example Defining a basic route.
178
+ # class MyAPI < Grape::API
179
+ # route(:any, '/hello') do
180
+ # {hello: 'world'}
181
+ # end
182
+ # end
183
+ def route(methods, paths = ['/'], requirements: nil, anchor: true, **route_options, &)
184
+ validate_requirements!(requirements)
185
+
186
+ http_methods = methods == :any ? '*' : methods
187
+ endpoint_description = inheritable_setting.route_description
188
+
189
+ # +params+, +requirements+ and +anchor+ each travel as their own endpoint
190
+ # input; the route-options bag keeps the description's other keys
191
+ # (+success+, +tags+, …) plus any custom options.
192
+ params = prepare_params(endpoint_description[:params])
193
+ all_route_options = endpoint_description.except(:params)
194
+ all_route_options.deep_merge!(route_options) if route_options.present?
195
+
196
+ new_endpoint = Grape::Endpoint.new(
197
+ inheritable_setting,
198
+ http_methods:,
199
+ path: paths,
200
+ api: self,
201
+ params:,
202
+ requirements:,
203
+ anchor:,
204
+ route_options: all_route_options,
205
+ &
206
+ )
207
+ endpoints << new_endpoint unless endpoints.include?(new_endpoint)
208
+
209
+ inheritable_setting.route_end
210
+ reset_validations!
211
+ end
189
212
 
190
- # An array of API routes.
191
- def routes
192
- @routes ||= prepare_routes
213
+ Grape::HTTP_SUPPORTED_METHODS.each do |supported_method|
214
+ define_method supported_method.downcase do |path = '/', **options, &block|
215
+ route(supported_method, path, **options, &block)
193
216
  end
217
+ end
194
218
 
195
- # Remove all defined routes.
196
- def reset_routes!
197
- endpoints.each(&:reset_routes!)
198
- @routes = nil
219
+ # Declare a "namespace", which prefixes all subordinate routes with its
220
+ # name. Any endpoints within a namespace, group, resource or segment,
221
+ # etc., will share their parent context as well as any configuration
222
+ # done in the namespace context.
223
+ #
224
+ # @example
225
+ #
226
+ # namespace :foo do
227
+ # get 'bar' do
228
+ # # defines the endpoint: GET /foo/bar
229
+ # end
230
+ # end
231
+ def namespace(space = nil, requirements: nil, **options, &block)
232
+ return inheritable_setting.namespace_path unless space || block
233
+
234
+ validate_requirements!(requirements)
235
+
236
+ within_namespace do
237
+ nest(block) do
238
+ inheritable_setting.add_namespace(Grape::Namespace.new(space, requirements:, **options)) if space
239
+ end
199
240
  end
241
+ end
200
242
 
201
- def reset_endpoints!
202
- @endpoints = []
203
- end
243
+ alias group namespace
244
+ alias resource namespace
245
+ alias resources namespace
246
+ alias segment namespace
204
247
 
205
- # This method allows you to quickly define a parameter route segment
206
- # in your API.
207
- #
208
- # @param param [Symbol] The name of the parameter you wish to declare.
209
- # @option options [Regexp] You may supply a regular expression that the declared parameter must meet.
210
- def route_param(param, options = {}, &block)
211
- options = options.dup
248
+ # An array of API routes.
249
+ def routes
250
+ @routes ||= endpoints.map(&:routes).flatten
251
+ end
212
252
 
213
- options[:requirements] = {
214
- param.to_sym => options[:requirements]
215
- } if options[:requirements].is_a?(Regexp)
253
+ # This method allows you to quickly define a parameter route segment
254
+ # in your API.
255
+ #
256
+ # @param param [Symbol] The name of the parameter you wish to declare.
257
+ # @option options [Regexp, Class, Symbol] The constraint the declared parameter must meet — a Regexp, or a capture type such as +Integer+.
258
+ def route_param(param, requirements: nil, type: nil, **, &)
259
+ # The param is named here, so the constraint is its own: nest whatever
260
+ # it is, not just a Regexp. A Hash would name the param twice, or key a
261
+ # capture this namespace does not introduce, and belongs on +namespace+.
262
+ raise ArgumentError, "route_param :`#{param}` constrains :`#{param}`; pass the constraint itself, or a Hash of requirements to the enclosing namespace" if requirements.respond_to?(:to_hash)
263
+
264
+ param_requirements = requirements ? { param.to_sym => requirements } : requirements
265
+
266
+ Grape::Validations::ParamsScope.new(api: self) do
267
+ requires param, type: type
268
+ end if type
269
+
270
+ namespace(":#{param}", requirements: param_requirements, **, &)
271
+ end
216
272
 
217
- Grape::Validations::ParamsScope.new(api: self) do
218
- requires param, type: options[:type]
219
- end if options.key?(:type)
273
+ # @return array of defined versions
274
+ def versions
275
+ @versions ||= []
276
+ end
277
+
278
+ private
279
+
280
+ # Requirements are keyed by param name and merged across namespaces, so
281
+ # anything else has nothing to attach to. Rejected here rather than at
282
+ # the merge, which runs on the first request that builds the routes.
283
+ def validate_requirements!(requirements)
284
+ return if requirements.nil? || requirements.respond_to?(:to_hash)
285
+
286
+ raise ArgumentError, "requirements must be a Hash of param name => constraint, got `#{requirements.class}`"
287
+ end
288
+
289
+ # Compose a route's params: the declared params (+params do … end+) deep-merged
290
+ # with any documented alongside +desc ..., params:+ (+description_params+).
291
+ def prepare_params(description_params)
292
+ endpoint_params = inheritable_setting.params_documentation || {}
293
+ return endpoint_params if description_params.blank?
294
+
295
+ endpoint_params.deep_merge(description_params)
296
+ end
220
297
 
221
- namespace(":#{param}", options, &block)
298
+ # Remove all defined routes.
299
+ def reset_routes!
300
+ endpoints.each(&:reset_routes!)
301
+ @routes = nil
302
+ end
303
+
304
+ def reset_endpoints!
305
+ @endpoints = []
306
+ end
307
+
308
+ # Re-mount +mounts+, replacing any endpoint already mounted for the same
309
+ # base API rather than adding a second one. Called by
310
+ # {Grape::API.refresh_mount_step} when a class-level method runs after the
311
+ # API was mounted.
312
+ def refresh_mounted_api(mounts, opts = {})
313
+ drop_endpoints_mounted_for(mounts)
314
+ mount(mounts, opts)
315
+ end
316
+
317
+ # A mounted Grape API is stored as the throwaway instance +mount+ built
318
+ # for it, never as the class that was written, so endpoints are matched on
319
+ # the base API both of them share.
320
+ def drop_endpoints_mounted_for(mounts)
321
+ normalize_mounts(mounts).each_key do |app|
322
+ endpoints.delete_if { |endpoint| same_mounted_app?(endpoint.mounted_app, app) }
222
323
  end
324
+ end
325
+
326
+ # A bare app mounts at the root. The test is +Hash+ rather than
327
+ # +respond_to?(:each_pair)+ because a Struct or an OpenStruct answers that
328
+ # too, and neither can express an app => path mapping — their keys are
329
+ # member names. Reading one as a mapping would silently mount nonsense
330
+ # instead of mounting the app itself.
331
+ def normalize_mounts(mounts)
332
+ mounts.is_a?(Hash) ? mounts : { mounts => '/' }
333
+ end
334
+
335
+ # Two mounts refer to the same app when they share the same base Grape
336
+ # API. +mount+ turns every mounted Grape API into a throwaway
337
+ # +mount_instance+ (a fresh +Class.new+ per mount), so object identity
338
+ # never holds across mounts; comparing the base is the real signal.
339
+ # Plain Rack apps have no base and are mounted as-is, so they fall back
340
+ # to object identity.
341
+ def same_mounted_app?(mounted, app)
342
+ return mounted.base.equal?(app.base) if mounted.respond_to?(:base) && app.respond_to?(:base)
343
+
344
+ mounted.equal?(app)
345
+ end
223
346
 
224
- # @return array of defined versions
225
- def versions
226
- @versions ||= []
347
+ # Execute first the provided block, then each of the
348
+ # block passed in. Allows for simple 'before' setups
349
+ # of settings stack pushes.
350
+ def nest(*blocks, &block)
351
+ blocks.compact!
352
+ return instance_eval(&block) if blocks.empty?
353
+
354
+ evaluate_as_instance_with_configuration(block) if block
355
+ blocks.each { |b| evaluate_as_instance_with_configuration(b) }
356
+ reset_validations!
357
+ end
358
+
359
+ def evaluate_as_instance_with_configuration(block, lazy: false)
360
+ lazy_block = Grape::Util::Lazy::Block.new do |configuration|
361
+ value_for_configuration = configuration
362
+ self.configuration = value_for_configuration.evaluate if value_for_configuration.is_a?(Grape::Util::Lazy::Base)
363
+ response = instance_eval(&block)
364
+ self.configuration = value_for_configuration
365
+ response
227
366
  end
367
+ return lazy_block if @base && base_instance? && lazy
368
+
369
+ lazy_block.evaluate_from(configuration)
228
370
  end
229
371
  end
230
372
  end