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
@@ -1,28 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/exceptions/base'
4
-
5
3
  module Grape
6
4
  module Exceptions
7
- class ValidationErrors < Grape::Exceptions::Base
8
- ERRORS_FORMAT_KEY = 'grape.errors.format'
9
- DEFAULT_ERRORS_FORMAT = '%{attributes} %{message}'
10
-
11
- include Enumerable
12
-
5
+ class ValidationErrors < Base
13
6
  attr_reader :errors
14
7
 
15
- def initialize(errors: [], headers: {}, **_options)
16
- @errors = errors.group_by(&:params)
17
- super message: full_messages.join(', '), status: 400, headers: headers
18
- end
19
-
20
- def each
21
- errors.each_pair do |attribute, errors|
22
- errors.each do |error|
23
- yield attribute, error
24
- end
25
- end
8
+ def initialize(exceptions: [], headers: {})
9
+ @errors = exceptions.flat_map(&:errors).group_by(&:params)
10
+ super(message: full_messages.join(', '), status: 400, headers:)
26
11
  end
27
12
 
28
13
  def as_json(**_opts)
@@ -39,17 +24,28 @@ module Grape
39
24
  end
40
25
 
41
26
  def full_messages
42
- messages = map do |attributes, error|
43
- I18n.t(
44
- ERRORS_FORMAT_KEY,
45
- default: DEFAULT_ERRORS_FORMAT,
46
- attributes: translate_attributes(attributes),
47
- message: error.message
48
- )
27
+ messages = errors.flat_map do |attributes, errs|
28
+ errs.map do |error|
29
+ translate(
30
+ :format,
31
+ scope: 'grape.errors',
32
+ default: '%<attributes>s %<message>s',
33
+ attributes: translate_attributes(attributes),
34
+ message: error.message
35
+ )
36
+ end
49
37
  end
50
38
  messages.uniq!
51
39
  messages
52
40
  end
41
+
42
+ private
43
+
44
+ def translate_attributes(keys)
45
+ keys.map do |key|
46
+ translate(key, scope: 'grape.errors.attributes', default: key.to_s)
47
+ end.join(', ')
48
+ end
53
49
  end
54
50
  end
55
51
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Formatter
5
+ class Base
6
+ def self.call(_object, _env)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def self.inherited(klass)
11
+ super
12
+ Formatter.register(klass)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,13 +2,11 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Json
6
- class << self
7
- def call(object, _env)
8
- return object.to_json if object.respond_to?(:to_json)
5
+ class Json < Base
6
+ def self.call(object, _env)
7
+ return object.to_json if object.respond_to?(:to_json)
9
8
 
10
- ::Grape::Json.dump(object)
11
- end
9
+ ::Grape::Json.dump(object)
12
10
  end
13
11
  end
14
12
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module SerializableHash
5
+ class SerializableHash < Base
6
6
  class << self
7
7
  def call(object, _env)
8
8
  return object if object.is_a?(String)
@@ -19,15 +19,11 @@ module Grape
19
19
  end
20
20
 
21
21
  def serialize(object)
22
- if object.respond_to? :serializable_hash
23
- object.serializable_hash
24
- elsif array_serializable?(object)
25
- object.map(&:serializable_hash)
26
- elsif object.is_a?(Hash)
27
- object.transform_values { |v| serialize(v) }
28
- else
29
- object
30
- end
22
+ return object.serializable_hash if object.respond_to?(:serializable_hash)
23
+ return object.map(&:serializable_hash) if array_serializable?(object)
24
+ return object.transform_values { |v| serialize(v) } if object.is_a?(Hash)
25
+
26
+ object
31
27
  end
32
28
 
33
29
  def array_serializable?(object)
@@ -2,11 +2,9 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Txt
6
- class << self
7
- def call(object, _env)
8
- object.respond_to?(:to_txt) ? object.to_txt : object.to_s
9
- end
5
+ class Txt < Base
6
+ def self.call(object, _env)
7
+ object.respond_to?(:to_txt) ? object.to_txt : object.to_s
10
8
  end
11
9
  end
12
10
  end
@@ -2,13 +2,11 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Xml
6
- class << self
7
- def call(object, _env)
8
- return object.to_xml if object.respond_to?(:to_xml)
5
+ class Xml < Base
6
+ def self.call(object, _env)
7
+ return object.to_xml if object.respond_to?(:to_xml)
9
8
 
10
- raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
11
- end
9
+ raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
12
10
  end
13
11
  end
14
12
  end
@@ -2,34 +2,16 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- extend Util::Registrable
5
+ extend Grape::Util::Registry
6
6
 
7
- class << self
8
- def builtin_formatters
9
- @builtin_formatters ||= {
10
- json: Grape::Formatter::Json,
11
- jsonapi: Grape::Formatter::Json,
12
- serializable_hash: Grape::Formatter::SerializableHash,
13
- txt: Grape::Formatter::Txt,
14
- xml: Grape::Formatter::Xml
15
- }
16
- end
7
+ module_function
17
8
 
18
- def formatters(**options)
19
- builtin_formatters.merge(default_elements).merge!(options[:formatters] || {})
20
- end
9
+ DEFAULT_LAMBDA_FORMATTER = ->(obj, _env) { obj }
21
10
 
22
- def formatter_for(api_format, **options)
23
- spec = formatters(**options)[api_format]
24
- case spec
25
- when nil
26
- ->(obj, _env) { obj }
27
- when Symbol
28
- method(spec)
29
- else
30
- spec
31
- end
32
- end
11
+ def formatter_for(api_format, formatters)
12
+ return formatters[api_format] if formatters&.key?(api_format)
13
+
14
+ registry[api_format] || DEFAULT_LAMBDA_FORMATTER
33
15
  end
34
16
  end
35
17
  end
data/lib/grape/json.rb ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ if defined?(::MultiJSON)
5
+ # Since multi_json 1.21.0, MultiJSON.dump is deprecated in favor of
6
+ # MultiJSON.generate (removed in 2.0). Keep Grape's dump surface but route
7
+ # it to the non-deprecated name — identical output, no deprecation warning.
8
+ # https://github.com/sferik/multi_json/blob/v1.21.1/CHANGELOG.md#deprecated
9
+ module Json
10
+ ParseError = ::MultiJSON::ParseError
11
+
12
+ class << self
13
+ def dump(object)
14
+ ::MultiJSON.generate(object)
15
+ end
16
+
17
+ # parse is not deprecated; it's re-exposed (not renamed) because this
18
+ # facade is its own module and no longer inherits MultiJSON's methods.
19
+ def parse(source)
20
+ ::MultiJSON.parse(source)
21
+ end
22
+ end
23
+ end
24
+ elsif defined?(::MultiJson)
25
+ # Legacy multi_json (< 1.21) predates generate/parse and only exposes
26
+ # dump/load. Map Grape's surface onto them so the call sites stay
27
+ # engine-agnostic (these names are not deprecated on < 1.21).
28
+ module Json
29
+ # Mutually exclusive with the MultiJSON branch above; only one runs.
30
+ ParseError = ::MultiJson::ParseError # rubocop:disable Lint/ConstantReassignment
31
+
32
+ class << self
33
+ def dump(object)
34
+ ::MultiJson.dump(object)
35
+ end
36
+
37
+ def parse(source)
38
+ ::MultiJson.load(source)
39
+ end
40
+ end
41
+ end
42
+ else
43
+ Json = ::JSON
44
+ Json::ParseError = Json::ParserError
45
+ end
46
+ end
@@ -1,55 +1,56 @@
1
+ ---
1
2
  en:
2
3
  grape:
3
4
  errors:
4
- format: ! '%{attributes} %{message}'
5
+ format: '%{attributes} %{message}'
5
6
  messages:
6
- coerce: 'is invalid'
7
- presence: 'is missing'
8
- regexp: 'is invalid'
7
+ all_or_none: 'provide all or none of parameters'
8
+ at_least_one: 'are missing, at least one parameter must be provided'
9
9
  blank: 'is empty'
10
- values: 'does not have a valid value'
10
+ coerce: 'is invalid'
11
+ exactly_one: 'are missing, exactly one parameter must be provided'
11
12
  except_values: 'has a value not allowed'
12
- same_as: 'is not the same as %{parameter}'
13
- missing_vendor_option:
14
- problem: 'missing :vendor option'
15
- summary: 'when version using header, you must specify :vendor option'
16
- resolution: "eg: version 'v1', using: :header, vendor: 'twitter'"
17
- missing_mime_type:
18
- problem: 'missing mime type for %{new_format}'
19
- resolution:
20
- "you can choose existing mime type from Grape::ContentTypes::CONTENT_TYPES
21
- or add your own with content_type :%{new_format}, 'application/%{new_format}'
22
- "
23
- invalid_with_option_for_represent:
24
- problem: 'you must specify an entity class in the :with option'
25
- resolution: 'eg: represent User, :with => Entity::User'
26
- missing_option: 'you must specify :%{option} options'
27
- invalid_formatter: 'cannot convert %{klass} to %{to_format}'
28
- invalid_versioner_option:
29
- problem: 'unknown :using for versioner: %{strategy}'
30
- resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
31
- unknown_validator: 'unknown validator: %{validator_type}'
32
- unknown_options: 'unknown options: %{options}'
33
- unknown_parameter: 'unknown parameter: %{param}'
34
13
  incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
35
- mutual_exclusion: 'are mutually exclusive'
36
- at_least_one: 'are missing, at least one parameter must be provided'
37
- exactly_one: 'are missing, exactly one parameter must be provided'
38
- all_or_none: 'provide all or none of parameters'
39
- missing_group_type: 'group type is required'
40
- unsupported_group_type: 'group type must be Array, Hash, JSON or Array[JSON]'
41
- invalid_message_body:
42
- problem: "message body does not match declared format"
43
- resolution:
44
- "when specifying %{body_format} as content-type, you must pass valid
45
- %{body_format} in the request's 'body'
46
- "
47
- empty_message_body: 'empty message body supplied with %{body_format} content-type'
48
- too_many_multipart_files: "the number of uploaded files exceeded the system's configured limit (%{limit})"
14
+ internal_server_error: 'Internal Server Error'
49
15
  invalid_accept_header:
50
16
  problem: 'invalid accept header'
51
17
  resolution: '%{message}'
18
+ invalid_formatter: 'cannot convert %{klass} to %{to_format}'
19
+ invalid_message_body:
20
+ problem: 'message body does not match declared format'
21
+ resolution: 'when specifying %{body_format} as content-type, you must pass valid %{body_format} in the request''s ''body'' '
22
+ invalid_response: 'Invalid response'
52
23
  invalid_version_header:
53
24
  problem: 'invalid version header'
54
25
  resolution: '%{message}'
55
- invalid_response: 'Invalid response'
26
+ invalid_versioner_option:
27
+ problem: 'unknown :using for versioner: %{strategy}'
28
+ resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
29
+ invalid_with_option_for_represent:
30
+ problem: 'you must specify an entity class in the :with option'
31
+ resolution: 'eg: represent User, :with => Entity::User'
32
+ length: 'is expected to have length within %{min} and %{max}'
33
+ length_is: 'is expected to have length exactly equal to %{is}'
34
+ length_max: 'is expected to have length less than or equal to %{max}'
35
+ length_min: 'is expected to have length greater than or equal to %{min}'
36
+ missing_group_type: 'group type is required'
37
+ missing_mime_type:
38
+ problem: 'missing mime type for %{new_format}'
39
+ resolution: 'you can choose existing mime type from Grape::ContentTypes::CONTENT_TYPES or add your own with content_type :%{new_format}, ''application/%{new_format}'' '
40
+ missing_option: 'you must specify :%{option} options'
41
+ missing_vendor_option:
42
+ problem: 'missing :vendor option'
43
+ resolution: 'eg: version ''v1'', using: :header, vendor: ''twitter'''
44
+ summary: 'when version using header, you must specify :vendor option'
45
+ mutual_exclusion: 'are mutually exclusive'
46
+ oneof: 'does not match any of the allowed schemas'
47
+ presence: 'is missing'
48
+ regexp: 'is invalid'
49
+ same_as: 'is not the same as %{parameter}'
50
+ unknown_auth_strategy: 'unknown auth strategy: %{strategy}'
51
+ unknown_options: 'unknown options: %{options}'
52
+ unknown_parameter: 'unknown parameter: %{param}'
53
+ unknown_params_builder: 'unknown params_builder: %{params_builder_type}'
54
+ unknown_validator: 'unknown validator: %{validator_type}'
55
+ unsupported_group_type: 'group type must be Array, Hash, JSON or Array[JSON]'
56
+ values: 'does not have a valid value'
@@ -1,44 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/auth/basic'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Auth
8
- class Base
9
- include Helpers
10
-
11
- attr_accessor :options, :app, :env
6
+ class Base < Grape::Middleware::Base
7
+ def initialize(app, **options)
8
+ super
9
+ return unless options.key?(:type)
12
10
 
13
- def initialize(app, *options)
14
- @app = app
15
- @options = options.shift
11
+ @auth_strategy = Grape::Middleware::Auth::Strategies[options[:type]]
12
+ raise Grape::Exceptions::UnknownAuthStrategy.new(strategy: options[:type]) unless @auth_strategy
16
13
  end
17
14
 
18
- def call(env)
19
- dup._call(env)
20
- end
21
-
22
- def _call(env)
23
- self.env = env
24
-
25
- if options.key?(:type)
26
- auth_proc = options[:proc]
27
- auth_proc_context = context
28
-
29
- strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
30
-
31
- throw(:error, status: 401, message: 'API Authorization Failed.') if strategy_info.blank?
32
-
33
- strategy = strategy_info.create(@app, options) do |*args|
34
- auth_proc_context.instance_exec(*args, &auth_proc)
35
- end
36
-
37
- strategy.call(env)
38
-
39
- else
40
- app.call(env)
41
- end
15
+ def call!(env)
16
+ @env = env
17
+ @auth_strategy.create(app, options) do |*args|
18
+ context.instance_exec(*args, &options[:proc])
19
+ end.call(env)
42
20
  end
43
21
  end
44
22
  end
@@ -1,45 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/auth/basic'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Auth
8
6
  module DSL
9
- extend ActiveSupport::Concern
10
-
11
- module ClassMethods
12
- # Add an authentication type to the API. Currently
13
- # only `:http_basic`, `:http_digest` are supported.
14
- def auth(type = nil, options = {}, &block)
15
- if type
16
- namespace_inheritable(:auth, options.reverse_merge(type: type.to_sym, proc: block))
17
- use Grape::Middleware::Auth::Base, namespace_inheritable(:auth)
18
- else
19
- namespace_inheritable(:auth)
20
- end
21
- end
7
+ def auth(type = nil, *legacy_options, **options, &block)
8
+ namespace_inheritable = inheritable_setting.namespace_inheritable
9
+ return namespace_inheritable[:auth] unless type
22
10
 
23
- # Add HTTP Basic authorization to the API.
24
- #
25
- # @param [Hash] options A hash of options.
26
- # @option options [String] :realm "API Authorization" The HTTP Basic realm.
27
- def http_basic(options = {}, &block)
28
- options[:realm] ||= 'API Authorization'
29
- auth :http_basic, options, &block
30
- end
11
+ options = merge_legacy_auth_options(:auth, legacy_options, options)
12
+ namespace_inheritable[:auth] = { type: type.to_sym, proc: block }.merge!(options)
13
+ use Grape::Middleware::Auth::Base, namespace_inheritable[:auth]
14
+ end
31
15
 
32
- def http_digest(options = {}, &block)
33
- options[:realm] ||= 'API Authorization'
16
+ # Add HTTP Basic authorization to the API.
17
+ #
18
+ # @param options [Hash] a hash of options
19
+ # @option options [String] :realm "API Authorization" the HTTP Basic realm
20
+ def http_basic(*legacy_options, **options, &)
21
+ options = merge_legacy_auth_options(:http_basic, legacy_options, options)
22
+ options[:realm] ||= 'API Authorization'
23
+ auth(:http_basic, **options, &)
24
+ end
34
25
 
35
- if options[:realm].respond_to?(:values_at)
36
- options[:realm][:opaque] ||= 'secret'
37
- else
38
- options[:opaque] ||= 'secret'
39
- end
26
+ def http_digest(*legacy_options, **options, &)
27
+ options = merge_legacy_auth_options(:http_digest, legacy_options, options)
28
+ options[:realm] ||= 'API Authorization'
40
29
 
41
- auth :http_digest, options, &block
30
+ if options[:realm].respond_to?(:values_at)
31
+ options[:realm][:opaque] ||= 'secret'
32
+ else
33
+ options[:opaque] ||= 'secret'
42
34
  end
35
+
36
+ auth(:http_digest, **options, &)
37
+ end
38
+
39
+ private
40
+
41
+ # @deprecated Passing a positional options Hash is deprecated; pass
42
+ # keyword arguments instead. Kept so downstream callers keep working
43
+ # through the deprecation cycle.
44
+ def merge_legacy_auth_options(method_name, legacy_options, options)
45
+ return options if legacy_options.empty?
46
+
47
+ Grape.deprecator.warn("Passing a positional options Hash to `#{method_name}` is deprecated. Pass keyword arguments instead.")
48
+ legacy_options.first.merge(options)
43
49
  end
44
50
  end
45
51
  end
@@ -12,8 +12,7 @@ module Grape
12
12
 
13
13
  def auth_strategies
14
14
  @auth_strategies ||= {
15
- http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) { [settings[:realm]] }),
16
- http_digest: StrategyInfo.new(Rack::Auth::Digest::MD5, ->(settings) { [settings[:realm], settings[:opaque]] })
15
+ http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) { [settings[:realm]] })
17
16
  }
18
17
  end
19
18
 
@@ -1,30 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/dsl/headers'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  class Base
8
- include Helpers
9
-
10
- attr_reader :app, :env, :options
11
-
12
- TEXT_HTML = 'text/html'
13
-
14
6
  include Grape::DSL::Headers
15
7
 
8
+ attr_reader :app, :env, :options, :config
9
+
16
10
  # @param [Rack Application] app The standard argument for a Rack middleware.
17
- # @param [Hash] options A hash of options, simply stored for use by subclasses.
18
- def initialize(app, *options)
11
+ # @param [Hash] options Options forwarded to the subclass. When the
12
+ # subclass declares an `Options` Data class, the kwargs are routed
13
+ # through it and exposed via {#config}; {#options} keeps returning a
14
+ # frozen Hash representation for back-compat with subclasses that read
15
+ # `options[:key]`. Otherwise the kwargs are deep-merged with the
16
+ # subclass's `DEFAULT_OPTIONS` Hash (legacy path) and frozen.
17
+ def initialize(app, **options)
19
18
  @app = app
20
- @options = options.any? ? default_options.merge(options.shift) : default_options
19
+ if (config_class = options_data_class)
20
+ @config = config_class.new(**options)
21
+ @options = @config.to_h.freeze
22
+ else
23
+ @options = merge_default_options(options).freeze
24
+ end
21
25
  @app_response = nil
22
26
  end
23
27
 
24
- def default_options
25
- {}
26
- end
27
-
28
28
  def call(env)
29
29
  dup.call!(env).to_a
30
30
  end
@@ -57,40 +57,63 @@ module Grape
57
57
  # @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
58
58
  def after; end
59
59
 
60
- def response
61
- return @app_response if @app_response.is_a?(Rack::Response)
62
-
63
- @app_response = Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
60
+ def rack_request
61
+ @rack_request ||= Rack::Request.new(env)
64
62
  end
65
63
 
66
- def content_type_for(format)
67
- HashWithIndifferentAccess.new(content_types)[format]
64
+ def context
65
+ env[Grape::Env::API_ENDPOINT]
68
66
  end
69
67
 
70
- def content_types
71
- ContentTypes.content_types_for(options[:content_types])
72
- end
68
+ def response
69
+ return @app_response if @app_response.is_a?(Rack::Response)
73
70
 
74
- def content_type
75
- content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
71
+ @app_response = Rack::Response[*@app_response]
76
72
  end
77
73
 
78
- def mime_types
79
- @mime_type ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
80
- types_without_params[v.split(';').first] = k
81
- end
74
+ def query_params
75
+ rack_request.GET
76
+ rescue *Grape::RACK_ERRORS
77
+ raise Grape::Exceptions::RequestError
82
78
  end
83
79
 
84
80
  private
85
81
 
86
82
  def merge_headers(response)
87
- return unless headers.is_a?(Hash)
83
+ return if @header.blank?
88
84
 
89
85
  case response
90
- when Rack::Response then response.headers.merge!(headers)
91
- when Array then response[1].merge!(headers)
86
+ when Rack::Response then response.headers.merge!(@header)
87
+ when Array then response[1].merge!(@header)
92
88
  end
93
89
  end
90
+
91
+ def merge_default_options(options)
92
+ return default_options.deep_merge(options) if respond_to?(:default_options)
93
+
94
+ default_options_constant&.deep_merge(options) || options
95
+ end
96
+
97
+ # self.class::Options honours middleware inheritance (e.g. Versioner::Path
98
+ # → Versioner::Base) and, unlike const_defined?, never resolves a
99
+ # top-level ::Options on Object. Absent an own/inherited Options class,
100
+ # the lookup raises NameError and we fall back to the legacy path.
101
+ def options_data_class
102
+ self.class::Options
103
+ rescue NameError
104
+ nil
105
+ end
106
+
107
+ # Same idea as {#options_data_class} for the legacy DEFAULT_OPTIONS Hash.
108
+ def default_options_constant
109
+ self.class::DEFAULT_OPTIONS
110
+ rescue NameError
111
+ nil
112
+ end
113
+
114
+ def try_scrub(obj)
115
+ obj.respond_to?(:valid_encoding?) && !obj.valid_encoding? ? obj.scrub : obj
116
+ end
94
117
  end
95
118
  end
96
119
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Middleware
5
+ # Mixin for per-middleware +Options+ +Data+ classes that need to keep
6
+ # accepting legacy +data[:key]+ Hash-style access while nudging callers
7
+ # toward the named accessor. Emits a +Grape.deprecator+ warning then
8
+ # forwards to +public_send(key)+.
9
+ module DeprecatedOptionsHashAccess
10
+ def [](key)
11
+ Grape.deprecator.warn(
12
+ "`#{self.class.name}#[]` is deprecated. " \
13
+ "Use the named accessor `#{key}` instead."
14
+ )
15
+ public_send(key) if members.include?(key)
16
+ end
17
+ end
18
+ end
19
+ end