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
data/lib/grape/cookies.rb CHANGED
@@ -2,42 +2,45 @@
2
2
 
3
3
  module Grape
4
4
  class Cookies
5
- def initialize
6
- @cookies = {}
7
- @send_cookies = {}
8
- end
5
+ extend Forwardable
9
6
 
10
- def read(request)
11
- request.cookies.each do |name, value|
12
- @cookies[name.to_s] = value
13
- end
14
- end
7
+ DELETED_COOKIES_ATTRS = {
8
+ max_age: '0',
9
+ value: '',
10
+ expires: Time.at(0)
11
+ }.freeze
15
12
 
16
- def write(header)
17
- @cookies.select { |key, _value| @send_cookies[key] == true }.each do |name, value|
18
- cookie_value = value.is_a?(Hash) ? value : { value: value }
19
- Rack::Utils.set_cookie_header! header, name, cookie_value
20
- end
13
+ def_delegators :cookies, :[], :each
14
+
15
+ def initialize(rack_cookies)
16
+ @cookies = ActiveSupport::HashWithIndifferentAccess.new(rack_cookies)
17
+ @send_cookies = nil
21
18
  end
22
19
 
23
- def [](name)
24
- @cookies[name.to_s]
20
+ def response_cookies
21
+ return unless @send_cookies
22
+
23
+ send_cookies.each do |name|
24
+ yield name, cookies[name]
25
+ end
25
26
  end
26
27
 
27
28
  def []=(name, value)
28
- @cookies[name.to_s] = value
29
- @send_cookies[name.to_s] = true
29
+ cookies[name] = value
30
+ send_cookies << name
30
31
  end
31
32
 
32
- def each(&block)
33
- @cookies.each(&block)
33
+ # see https://github.com/rack/rack/blob/main/lib/rack/utils.rb#L338-L340
34
+ def delete(name, **opts)
35
+ self.[]=(name, opts.merge(DELETED_COOKIES_ATTRS))
34
36
  end
35
37
 
36
- # rubocop:disable Layout/SpaceBeforeBrackets
37
- def delete(name, **opts)
38
- options = opts.merge(value: 'deleted', expires: Time.at(0))
39
- self.[]=(name, options)
38
+ private
39
+
40
+ attr_reader :cookies
41
+
42
+ def send_cookies
43
+ @send_cookies ||= Set.new
40
44
  end
41
- # rubocop:enable Layout/SpaceBeforeBrackets
42
45
  end
43
46
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ class DeclaredParamsHandler
5
+ def initialize(include_missing: true, evaluate_given: false, stringify: false, contract_key_map: nil)
6
+ @include_missing = include_missing
7
+ @evaluate_given = evaluate_given
8
+ @stringify = stringify
9
+ @contract_key_map = contract_key_map
10
+ end
11
+
12
+ def call(passed_params, declared_params, route_params, renamed_params)
13
+ recursive_declared(
14
+ passed_params,
15
+ declared_params:,
16
+ route_params:,
17
+ renamed_params:
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ def recursive_declared(passed_params, declared_params:, route_params:, renamed_params:, params_nested_path: [])
24
+ res = if passed_params.is_a?(Array)
25
+ passed_params.map do |passed_param|
26
+ recursive_declared(passed_param, declared_params:, params_nested_path:, renamed_params:, route_params:)
27
+ end
28
+ else
29
+ declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
30
+ end
31
+
32
+ @contract_key_map&.each { |key_map| key_map.write(passed_params, res) }
33
+
34
+ res
35
+ end
36
+
37
+ def declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
38
+ declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
39
+ next if @evaluate_given && !declared_param_attr.scope.attr_meets_dependency?(passed_params)
40
+
41
+ declared_hash_attr(
42
+ passed_params,
43
+ declared_param: declared_param_attr.key,
44
+ params_nested_path:,
45
+ memo:,
46
+ renamed_params:,
47
+ route_params:
48
+ )
49
+ end
50
+ end
51
+
52
+ def declared_hash_attr(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)
53
+ return declare_leaf(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:) unless declared_param.is_a?(Hash)
54
+
55
+ declared_param.each_pair do |parent, children|
56
+ declare_nested(passed_params, parent:, children:, params_nested_path:, memo:, renamed_params:, route_params:)
57
+ end
58
+ end
59
+
60
+ def declare_nested(passed_params, parent:, children:, params_nested_path:, memo:, renamed_params:, route_params:)
61
+ return unless @include_missing || passed_params.key?(parent)
62
+
63
+ memo_key = build_memo_key(params_nested_path, parent, renamed_params)
64
+ passed_children = passed_params[parent] || passed_params.class.new
65
+ nested_path = nested_path_for(params_nested_path, parent)
66
+
67
+ memo[memo_key] = handle_passed_param(nested_path, route_params:, has_passed_children: passed_children.any?) do
68
+ recursive_declared(
69
+ passed_children,
70
+ declared_params: children,
71
+ params_nested_path: nested_path,
72
+ renamed_params:,
73
+ route_params:
74
+ )
75
+ end
76
+ end
77
+
78
+ # The declared param has no children. Find its value or set it to nil.
79
+ def declare_leaf(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)
80
+ return unless @include_missing || (passed_params.respond_to?(:key?) && passed_params.key?(declared_param))
81
+
82
+ memo_key = build_memo_key(params_nested_path, declared_param, renamed_params)
83
+ passed_param = passed_params[declared_param]
84
+
85
+ memo[memo_key] = passed_param || handle_passed_param(nested_path_for(params_nested_path, declared_param), route_params:) do
86
+ passed_param
87
+ end
88
+ end
89
+
90
+ def build_memo_key(params_nested_path, declared_param, renamed_params)
91
+ renamed_param_name = renamed_params[nested_path_for(params_nested_path, declared_param)]
92
+ param = renamed_param_name || declared_param
93
+ @stringify ? param.to_s : param.to_sym
94
+ end
95
+
96
+ def nested_path_for(parent_path, key)
97
+ parent_path + [key.to_s]
98
+ end
99
+
100
+ def handle_passed_param(params_nested_path, route_params:, has_passed_children: false)
101
+ return yield if has_passed_children
102
+
103
+ key = params_nested_path.first
104
+ key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1
105
+
106
+ type = route_params.dig(key, :type)
107
+ return yield if type.nil?
108
+
109
+ return {} if type == 'Hash' && route_params.keys.none? { |k| k != key && k.start_with?("#{key}[") }
110
+ return [] if type == 'Array' || (type.start_with?('[') && !type.include?(','))
111
+ return Set.new if type == 'Set' || type.start_with?('#<Set', 'Set')
112
+
113
+ yield
114
+ end
115
+ end
116
+ end
@@ -1,12 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-types'
4
-
5
3
  module Grape
6
4
  module DryTypes
7
- # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
8
- # a container in this case. Check documentation for more information
9
- # https://dry-rb.org/gems/dry-types/1.2/getting-started/
10
- include Dry.Types()
5
+ # https://dry-rb.org/gems/dry-types/main/getting-started/
6
+ # limit to what Grape is using
7
+ include Dry.Types(:params, :coercible, :strict)
8
+
9
+ class StrictCache < Grape::Util::Cache
10
+ MAPPING = {
11
+ Grape::API::Boolean => DryTypes::Strict::Bool,
12
+ BigDecimal => DryTypes::Strict::Decimal,
13
+ Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
14
+ TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
15
+ FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
16
+ }.freeze
17
+
18
+ def initialize
19
+ super
20
+ @cache = Hash.new do |h, strict_type|
21
+ h[strict_type] = MAPPING.fetch(strict_type) do
22
+ DryTypes.wrapped_dry_types_const_get(DryTypes::Strict, strict_type)
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ class ParamsCache < Grape::Util::Cache
29
+ MAPPING = {
30
+ Grape::API::Boolean => DryTypes::Params::Bool,
31
+ BigDecimal => DryTypes::Params::Decimal,
32
+ Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
33
+ TrueClass => DryTypes::Params::Bool.constrained(eql: true),
34
+ FalseClass => DryTypes::Params::Bool.constrained(eql: false),
35
+ String => DryTypes::Coercible::String
36
+ }.freeze
37
+
38
+ def initialize
39
+ super
40
+ @cache = Hash.new do |h, params_type|
41
+ h[params_type] = MAPPING.fetch(params_type) do
42
+ DryTypes.wrapped_dry_types_const_get(DryTypes::Params, params_type)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def self.wrapped_dry_types_const_get(dry_type, type)
49
+ dry_type.const_get(type.name, false)
50
+ rescue NameError
51
+ raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])
52
+ end
11
53
  end
12
54
  end
@@ -2,66 +2,22 @@
2
2
 
3
3
  module Grape
4
4
  module DSL
5
- # Blocks can be executed before or after every API call, using `before`, `after`,
6
- # `before_validation` and `after_validation`.
7
- #
8
- # Before and after callbacks execute in the following order:
9
- #
10
- # 1. `before`
11
- # 2. `before_validation`
12
- # 3. _validations_
13
- # 4. `after_validation`
14
- # 5. _the API call_
15
- # 6. `after`
16
- #
17
- # Steps 4, 5 and 6 only happen if validation succeeds.
18
5
  module Callbacks
19
- extend ActiveSupport::Concern
6
+ # before: execute the given block before validation, coercion, or any endpoint
7
+ # before_validation: execute the given block after `before`, but prior to validation or coercion
8
+ # after_validation: execute the given block after validations and coercions, but before any endpoint code
9
+ # after: execute the given block after the endpoint code has run except in unsuccessful
10
+ # finally: execute the given block after the endpoint code even if unsuccessful
20
11
 
21
- include Grape::DSL::Configuration
22
-
23
- module ClassMethods
24
- # Execute the given block before validation, coercion, or any endpoint
25
- # code is executed.
26
- def before(&block)
27
- namespace_stackable(:befores, block)
28
- end
29
-
30
- # Execute the given block after `before`, but prior to validation or
31
- # coercion.
32
- def before_validation(&block)
33
- namespace_stackable(:before_validations, block)
34
- end
35
-
36
- # Execute the given block after validations and coercions, but before
37
- # any endpoint code.
38
- def after_validation(&block)
39
- namespace_stackable(:after_validations, block)
40
- end
41
-
42
- # Execute the given block after the endpoint code has run.
43
- def after(&block)
44
- namespace_stackable(:afters, block)
45
- end
46
-
47
- # Allows you to specify a something that will always be executed after a call
48
- # API call. Unlike the `after` block, this code will run even on
49
- # unsuccesful requests.
50
- # @example
51
- # class ExampleAPI < Grape::API
52
- # before do
53
- # ApiLogger.start
54
- # end
55
- # finally do
56
- # ApiLogger.close
57
- # end
58
- # end
59
- #
60
- # This will make sure that the ApiLogger is opened and closed around every
61
- # request
62
- # @param ensured_block [Proc] The block to be executed after every api_call
63
- def finally(&block)
64
- namespace_stackable(:finallies, block)
12
+ {
13
+ before: :befores,
14
+ before_validation: :before_validations,
15
+ after_validation: :after_validations,
16
+ after: :afters,
17
+ finally: :finallies
18
+ }.each do |method_name, plural_key|
19
+ define_method method_name do |&block|
20
+ inheritable_setting.namespace_stackable[plural_key] = block
65
21
  end
66
22
  end
67
23
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module DSL
5
+ module Declared
6
+ # Denotes a situation where a DSL method has been invoked in a
7
+ # filter which it should not yet be available in
8
+ class MethodNotYetAvailable < StandardError
9
+ def initialize(msg = '#declared is not available prior to parameter validation')
10
+ super
11
+ end
12
+ end
13
+
14
+ # A filtering method that will return a hash
15
+ # consisting only of keys that have been declared by a
16
+ # `params` statement against the current/target endpoint or parent
17
+ # namespaces.
18
+ # @param params [Hash] The initial hash to filter. Usually this will just be `params`
19
+ # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
20
+ # options. `:include_parent_namespaces` defaults to true, hence must be set to false if
21
+ # you want only to return params declared against the current/target endpoint.
22
+ def declared(passed_params, include_parent_namespaces: true, include_missing: true, evaluate_given: false, stringify: false)
23
+ raise MethodNotYetAvailable unless before_filter_passed
24
+
25
+ contract_key_map = inheritable_setting.namespace_stackable[:contract_key_map]
26
+ handler = DeclaredParamsHandler.new(include_missing:, evaluate_given:, stringify:, contract_key_map:)
27
+ declared_params = include_parent_namespaces ? inheritable_setting.route[:declared_params] : (inheritable_setting.namespace_stackable[:declared_params].last || [])
28
+ renamed_params = inheritable_setting.route[:renamed_params] || {}
29
+ route_params = options.dig(:route_options, :params) || {} # options = endpoint's option
30
+
31
+ handler.call(passed_params, declared_params, route_params, renamed_params)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -3,13 +3,14 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Desc
6
- include Grape::DSL::Settings
6
+ extend Grape::DSL::Settings
7
7
 
8
8
  # Add a description to the next namespace or function.
9
9
  # @param description [String] descriptive string for this endpoint
10
10
  # or namespace
11
11
  # @param options [Hash] other properties you can set to describe the
12
- # endpoint or namespace. Optional.
12
+ # endpoint or namespace. Optional. Pass these as keyword arguments;
13
+ # passing a positional options Hash is deprecated.
13
14
  # @option options :detail [String] additional detail about this endpoint
14
15
  # @option options :summary [String] summary for this endpoint
15
16
  # @option options :params [Hash] param types and info. normally, you set
@@ -49,70 +50,21 @@ module Grape
49
50
  # # ...
50
51
  # end
51
52
  #
52
- def desc(description, options = {}, &config_block)
53
- if config_block
54
- endpoint_configuration = if defined?(configuration)
55
- # When the instance is mounted - the configuration is executed on mount time
56
- if configuration.respond_to?(:evaluate)
57
- configuration.evaluate
58
- # Within `given` or `mounted blocks` the configuration is already evaluated
59
- elsif configuration.is_a?(Hash)
60
- configuration
61
- end
62
- end
63
- endpoint_configuration ||= {}
64
- config_class = desc_container(endpoint_configuration)
65
-
66
- config_class.configure do
67
- description description
68
- end
69
-
70
- config_class.configure(&config_block)
71
- ActiveSupport::Deprecation.warn('Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.') if options.any?
72
- options = config_class.settings
73
- else
74
- options = options.merge(description: description)
53
+ def desc(description, *legacy_options, **options, &config_block)
54
+ if legacy_options.any?
55
+ Grape.deprecator.warn('Passing a positional options Hash to `desc` is deprecated. Pass keyword arguments instead.')
56
+ options = legacy_options.first.merge(options)
75
57
  end
76
58
 
77
- namespace_setting :description, options
78
- route_setting :description, options
79
- end
80
-
81
- # Returns an object which configures itself via an instance-context DSL.
82
- def desc_container(endpoint_configuration)
83
- Module.new do
84
- include Grape::Util::StrictHashConfiguration.module(
85
- :summary,
86
- :description,
87
- :detail,
88
- :params,
89
- :entity,
90
- :http_codes,
91
- :named,
92
- :body_name,
93
- :headers,
94
- :hidden,
95
- :deprecated,
96
- :is_array,
97
- :nickname,
98
- :produces,
99
- :consumes,
100
- :security,
101
- :tags,
102
- :default
103
- )
104
- config_context.define_singleton_method(:configuration) do
105
- endpoint_configuration
59
+ settings =
60
+ if config_block
61
+ endpoint_config = defined?(configuration) ? configuration : nil
62
+ Grape::Util::ApiDescription.new(description, endpoint_config, &config_block).settings
63
+ else
64
+ options.merge(description:)
106
65
  end
107
-
108
- def config_context.success(*args)
109
- entity(*args)
110
- end
111
-
112
- def config_context.failure(*args)
113
- http_codes(*args)
114
- end
115
- end
66
+ inheritable_setting.namespace[:description] = settings
67
+ inheritable_setting.route[:description] = settings
116
68
  end
117
69
  end
118
70
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module DSL
5
+ module Entity
6
+ # Allows you to make use of Grape Entities by setting
7
+ # the response body to the serializable hash of the
8
+ # entity provided in the `:with` option. This has the
9
+ # added benefit of automatically passing along environment
10
+ # and version information to the serialization, making it
11
+ # very easy to do conditional exposures. See Entity docs
12
+ # for more info.
13
+ #
14
+ # @param args [Array] either `(object)` or `(key, object)` where key is a Symbol
15
+ # used to nest the representation under that key in the response body.
16
+ # @param root [Symbol, String, nil] wraps the representation under this root key.
17
+ # @param with [Class, nil] the entity class to use for representation.
18
+ # If omitted, the entity class is inferred from the object via {#entity_class_for_obj}.
19
+ # @param options [Hash] additional options forwarded to the entity's `represent` call.
20
+ #
21
+ # @example
22
+ #
23
+ # get '/users/:id' do
24
+ # present User.find(params[:id]),
25
+ # with: API::Entities::User,
26
+ # admin: current_user.admin?
27
+ # end
28
+ def present(*args, root: nil, with: nil, **options)
29
+ key, object = args.count == 2 && args.first.is_a?(Symbol) ? args : [nil, args.first]
30
+ entity_class = with || entity_class_for_obj(object)
31
+ representation = entity_class ? entity_representation_for(entity_class, object, options) : object
32
+ representation = { root => representation } if root
33
+
34
+ if key
35
+ representation = body&.merge(key => representation) || { key => representation }
36
+ elsif entity_class.present? && body
37
+ raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
38
+
39
+ representation = body.merge(representation)
40
+ end
41
+
42
+ body representation
43
+ end
44
+
45
+ # Attempt to locate the Entity class for a given object, if not given
46
+ # explicitly. This is done by looking for the presence of Klass::Entity,
47
+ # where Klass is the class of the `object` parameter, or one of its
48
+ # ancestors.
49
+ # @param object [Object] the object to locate the Entity class for
50
+ # @return [Class] the located Entity class, or nil if none is found
51
+ def entity_class_for_obj(object)
52
+ klass = object_class(object)
53
+
54
+ representations = inheritable_setting.namespace_stackable_with_hash(:representations)
55
+ if representations
56
+ potential = klass.ancestors.detect { |potential| representations.key?(potential) }
57
+ return representations[potential] if potential && representations[potential]
58
+ end
59
+
60
+ return unless klass.const_defined?(:Entity)
61
+
62
+ entity = klass.const_get(:Entity)
63
+ entity if entity.respond_to?(:represent)
64
+ end
65
+
66
+ private
67
+
68
+ # Resolves the class used to look up the Entity for +object+.
69
+ # @param object [Object] the object to represent.
70
+ # @return [Class] the object's collection element class, wrapped class, or its own class.
71
+ def object_class(object)
72
+ return object.klass if object.respond_to?(:klass)
73
+ return object.first.class if object.respond_to?(:first)
74
+
75
+ object.class
76
+ end
77
+
78
+ # @param entity_class [Class] the entity class to use for representation.
79
+ # @param object [Object] the object to represent.
80
+ # @param options [Hash] additional options forwarded to the entity's `represent` call.
81
+ # @return the representation of the given object as done through the given entity_class.
82
+ def entity_representation_for(entity_class, object, options)
83
+ embeds = env.key?(Grape::Env::API_VERSION) ? { env:, version: env[Grape::Env::API_VERSION] } : { env: }
84
+ entity_class.represent(object, **embeds, **options)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -10,9 +10,9 @@ module Grape
10
10
  # 4. Delete a specifc header key-value pair
11
11
  def header(key = nil, val = nil)
12
12
  if key
13
- val ? header[key.to_s] = val : header.delete(key.to_s)
13
+ val ? header[key] = val : header.delete(key)
14
14
  else
15
- @header ||= {}
15
+ @header ||= Grape::Util::Header.new
16
16
  end
17
17
  end
18
18
  alias headers header