grape 1.0.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (284) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +346 -39
  3. data/LICENSE +1 -1
  4. data/README.md +658 -90
  5. data/UPGRADING.md +472 -17
  6. data/grape.gemspec +17 -6
  7. data/lib/grape/api/helpers.rb +2 -0
  8. data/lib/grape/api/instance.rb +279 -0
  9. data/lib/grape/api.rb +132 -176
  10. data/lib/grape/config.rb +34 -0
  11. data/lib/grape/content_types.rb +34 -0
  12. data/lib/grape/cookies.rb +4 -0
  13. data/lib/grape/dsl/api.rb +2 -0
  14. data/lib/grape/dsl/callbacks.rb +22 -0
  15. data/lib/grape/dsl/configuration.rb +2 -0
  16. data/lib/grape/dsl/desc.rb +44 -12
  17. data/lib/grape/dsl/headers.rb +2 -0
  18. data/lib/grape/dsl/helpers.rb +11 -6
  19. data/lib/grape/dsl/inside_route.rb +116 -36
  20. data/lib/grape/dsl/logger.rb +2 -0
  21. data/lib/grape/dsl/middleware.rb +12 -3
  22. data/lib/grape/dsl/parameters.rb +34 -16
  23. data/lib/grape/dsl/request_response.rb +13 -8
  24. data/lib/grape/dsl/routing.rb +19 -12
  25. data/lib/grape/dsl/settings.rb +22 -4
  26. data/lib/grape/dsl/validations.rb +24 -4
  27. data/lib/grape/eager_load.rb +20 -0
  28. data/lib/grape/endpoint.rb +66 -57
  29. data/lib/grape/error_formatter/base.rb +2 -0
  30. data/lib/grape/error_formatter/json.rb +6 -4
  31. data/lib/grape/error_formatter/txt.rb +10 -3
  32. data/lib/grape/error_formatter/xml.rb +6 -4
  33. data/lib/grape/error_formatter.rb +4 -2
  34. data/lib/grape/exceptions/base.rb +22 -14
  35. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  36. data/lib/grape/exceptions/incompatible_option_values.rb +2 -1
  37. data/lib/grape/exceptions/invalid_accept_header.rb +2 -1
  38. data/lib/grape/exceptions/invalid_formatter.rb +2 -1
  39. data/lib/grape/exceptions/invalid_message_body.rb +2 -1
  40. data/lib/grape/exceptions/invalid_response.rb +11 -0
  41. data/lib/grape/exceptions/invalid_version_header.rb +2 -1
  42. data/lib/grape/exceptions/invalid_versioner_option.rb +2 -1
  43. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -1
  44. data/lib/grape/exceptions/method_not_allowed.rb +2 -1
  45. data/lib/grape/exceptions/missing_group_type.rb +2 -1
  46. data/lib/grape/exceptions/missing_mime_type.rb +2 -1
  47. data/lib/grape/exceptions/missing_option.rb +2 -1
  48. data/lib/grape/exceptions/missing_vendor_option.rb +2 -1
  49. data/lib/grape/exceptions/unknown_options.rb +2 -1
  50. data/lib/grape/exceptions/unknown_parameter.rb +2 -1
  51. data/lib/grape/exceptions/unknown_validator.rb +2 -1
  52. data/lib/grape/exceptions/unsupported_group_type.rb +2 -1
  53. data/lib/grape/exceptions/validation.rb +5 -4
  54. data/lib/grape/exceptions/validation_array_errors.rb +2 -0
  55. data/lib/grape/exceptions/validation_errors.rb +16 -13
  56. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
  57. data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
  58. data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
  59. data/lib/grape/extensions/hash.rb +2 -0
  60. data/lib/grape/extensions/hashie/mash.rb +2 -0
  61. data/lib/grape/formatter/json.rb +3 -0
  62. data/lib/grape/formatter/serializable_hash.rb +4 -1
  63. data/lib/grape/formatter/txt.rb +2 -0
  64. data/lib/grape/formatter/xml.rb +3 -0
  65. data/lib/grape/formatter.rb +5 -3
  66. data/lib/grape/http/headers.rb +50 -18
  67. data/lib/grape/locale/en.yml +3 -1
  68. data/lib/grape/middleware/auth/base.rb +7 -7
  69. data/lib/grape/middleware/auth/dsl.rb +2 -0
  70. data/lib/grape/middleware/auth/strategies.rb +2 -0
  71. data/lib/grape/middleware/auth/strategy_info.rb +2 -0
  72. data/lib/grape/middleware/base.rb +12 -7
  73. data/lib/grape/middleware/error.rb +75 -61
  74. data/lib/grape/middleware/filter.rb +2 -0
  75. data/lib/grape/middleware/formatter.rb +17 -13
  76. data/lib/grape/middleware/globals.rb +2 -0
  77. data/lib/grape/middleware/helpers.rb +12 -0
  78. data/lib/grape/middleware/stack.rb +15 -5
  79. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -5
  80. data/lib/grape/middleware/versioner/header.rb +13 -9
  81. data/lib/grape/middleware/versioner/param.rb +4 -1
  82. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +5 -1
  83. data/lib/grape/middleware/versioner/path.rb +5 -1
  84. data/lib/grape/middleware/versioner.rb +2 -0
  85. data/lib/grape/namespace.rb +15 -3
  86. data/lib/grape/parser/json.rb +3 -1
  87. data/lib/grape/parser/xml.rb +3 -1
  88. data/lib/grape/parser.rb +4 -2
  89. data/lib/grape/path.rb +16 -3
  90. data/lib/grape/presenters/presenter.rb +2 -0
  91. data/lib/grape/request.rb +19 -9
  92. data/lib/grape/router/attribute_translator.rb +41 -8
  93. data/lib/grape/router/pattern.rb +22 -18
  94. data/lib/grape/router/route.rb +16 -30
  95. data/lib/grape/router.rb +37 -28
  96. data/lib/grape/{serve_file → serve_stream}/file_body.rb +3 -1
  97. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +3 -1
  98. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +10 -8
  99. data/lib/grape/util/base_inheritable.rb +43 -0
  100. data/lib/grape/util/cache.rb +20 -0
  101. data/lib/grape/util/endpoint_configuration.rb +8 -0
  102. data/lib/grape/util/env.rb +19 -17
  103. data/lib/grape/util/inheritable_setting.rb +3 -3
  104. data/lib/grape/util/inheritable_values.rb +7 -25
  105. data/lib/grape/util/json.rb +2 -0
  106. data/lib/grape/util/lazy_block.rb +27 -0
  107. data/lib/grape/util/lazy_object.rb +43 -0
  108. data/lib/grape/util/lazy_value.rb +99 -0
  109. data/lib/grape/util/registrable.rb +2 -0
  110. data/lib/grape/util/reverse_stackable_values.rb +10 -35
  111. data/lib/grape/util/stackable_values.rb +21 -34
  112. data/lib/grape/util/strict_hash_configuration.rb +2 -0
  113. data/lib/grape/util/xml.rb +2 -0
  114. data/lib/grape/validations/attributes_iterator.rb +16 -6
  115. data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
  116. data/lib/grape/validations/params_scope.rb +128 -66
  117. data/lib/grape/validations/single_attribute_iterator.rb +24 -0
  118. data/lib/grape/validations/types/array_coercer.rb +65 -0
  119. data/lib/grape/validations/types/build_coercer.rb +52 -46
  120. data/lib/grape/validations/types/custom_type_coercer.rb +30 -51
  121. data/lib/grape/validations/types/custom_type_collection_coercer.rb +56 -0
  122. data/lib/grape/validations/types/dry_type_coercer.rb +76 -0
  123. data/lib/grape/validations/types/file.rb +22 -18
  124. data/lib/grape/validations/types/invalid_value.rb +24 -0
  125. data/lib/grape/validations/types/json.rb +47 -39
  126. data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
  127. data/lib/grape/validations/types/primitive_coercer.rb +67 -0
  128. data/lib/grape/validations/types/set_coercer.rb +40 -0
  129. data/lib/grape/validations/types/variant_collection_coercer.rb +5 -13
  130. data/lib/grape/validations/types.rb +26 -38
  131. data/lib/grape/validations/validator_factory.rb +8 -11
  132. data/lib/grape/validations/validators/all_or_none.rb +8 -12
  133. data/lib/grape/validations/validators/allow_blank.rb +4 -2
  134. data/lib/grape/validations/validators/as.rb +12 -0
  135. data/lib/grape/validations/validators/at_least_one_of.rb +7 -12
  136. data/lib/grape/validations/validators/base.rb +23 -15
  137. data/lib/grape/validations/validators/coerce.rb +47 -28
  138. data/lib/grape/validations/validators/default.rb +7 -6
  139. data/lib/grape/validations/validators/exactly_one_of.rb +10 -22
  140. data/lib/grape/validations/validators/except_values.rb +4 -2
  141. data/lib/grape/validations/validators/multiple_params_base.rb +19 -10
  142. data/lib/grape/validations/validators/mutual_exclusion.rb +8 -17
  143. data/lib/grape/validations/validators/presence.rb +4 -1
  144. data/lib/grape/validations/validators/regexp.rb +5 -2
  145. data/lib/grape/validations/validators/same_as.rb +27 -0
  146. data/lib/grape/validations/validators/values.rb +29 -9
  147. data/lib/grape/validations.rb +2 -0
  148. data/lib/grape/version.rb +3 -1
  149. data/lib/grape.rb +107 -73
  150. data/spec/grape/api/custom_validations_spec.rb +6 -3
  151. data/spec/grape/api/deeply_included_options_spec.rb +2 -0
  152. data/spec/grape/api/defines_boolean_in_params_spec.rb +39 -0
  153. data/spec/grape/api/inherited_helpers_spec.rb +116 -0
  154. data/spec/grape/api/instance_spec.rb +104 -0
  155. data/spec/grape/api/invalid_format_spec.rb +2 -0
  156. data/spec/grape/api/namespace_parameters_in_route_spec.rb +2 -0
  157. data/spec/grape/api/nested_helpers_spec.rb +2 -0
  158. data/spec/grape/api/optional_parameters_in_route_spec.rb +2 -0
  159. data/spec/grape/api/parameters_modification_spec.rb +3 -1
  160. data/spec/grape/api/patch_method_helpers_spec.rb +2 -0
  161. data/spec/grape/api/recognize_path_spec.rb +2 -0
  162. data/spec/grape/api/required_parameters_in_route_spec.rb +2 -0
  163. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +2 -0
  164. data/spec/grape/api/routes_with_requirements_spec.rb +61 -0
  165. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +49 -0
  166. data/spec/grape/api/shared_helpers_spec.rb +2 -0
  167. data/spec/grape/api_remount_spec.rb +473 -0
  168. data/spec/grape/api_spec.rb +775 -65
  169. data/spec/grape/config_spec.rb +19 -0
  170. data/spec/grape/dsl/callbacks_spec.rb +3 -1
  171. data/spec/grape/dsl/configuration_spec.rb +2 -0
  172. data/spec/grape/dsl/desc_spec.rb +42 -16
  173. data/spec/grape/dsl/headers_spec.rb +2 -0
  174. data/spec/grape/dsl/helpers_spec.rb +23 -2
  175. data/spec/grape/dsl/inside_route_spec.rb +185 -34
  176. data/spec/grape/dsl/logger_spec.rb +2 -0
  177. data/spec/grape/dsl/middleware_spec.rb +11 -1
  178. data/spec/grape/dsl/parameters_spec.rb +12 -9
  179. data/spec/grape/dsl/request_response_spec.rb +2 -0
  180. data/spec/grape/dsl/routing_spec.rb +13 -1
  181. data/spec/grape/dsl/settings_spec.rb +4 -2
  182. data/spec/grape/dsl/validations_spec.rb +2 -0
  183. data/spec/grape/endpoint/declared_spec.rb +848 -0
  184. data/spec/grape/endpoint_spec.rb +75 -515
  185. data/spec/grape/entity_spec.rb +19 -11
  186. data/spec/grape/exceptions/base_spec.rb +67 -0
  187. data/spec/grape/exceptions/body_parse_errors_spec.rb +2 -0
  188. data/spec/grape/exceptions/invalid_accept_header_spec.rb +2 -0
  189. data/spec/grape/exceptions/invalid_formatter_spec.rb +2 -1
  190. data/spec/grape/exceptions/invalid_response_spec.rb +13 -0
  191. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -1
  192. data/spec/grape/exceptions/missing_mime_type_spec.rb +2 -0
  193. data/spec/grape/exceptions/missing_option_spec.rb +2 -1
  194. data/spec/grape/exceptions/unknown_options_spec.rb +3 -2
  195. data/spec/grape/exceptions/unknown_validator_spec.rb +2 -1
  196. data/spec/grape/exceptions/validation_errors_spec.rb +17 -4
  197. data/spec/grape/exceptions/validation_spec.rb +3 -1
  198. data/spec/grape/extensions/param_builders/hash_spec.rb +2 -0
  199. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +2 -0
  200. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +2 -0
  201. data/spec/grape/integration/global_namespace_function_spec.rb +2 -0
  202. data/spec/grape/integration/rack_sendfile_spec.rb +14 -8
  203. data/spec/grape/integration/rack_spec.rb +25 -7
  204. data/spec/grape/loading_spec.rb +2 -0
  205. data/spec/grape/middleware/auth/base_spec.rb +2 -0
  206. data/spec/grape/middleware/auth/dsl_spec.rb +6 -4
  207. data/spec/grape/middleware/auth/strategies_spec.rb +3 -1
  208. data/spec/grape/middleware/base_spec.rb +10 -0
  209. data/spec/grape/middleware/error_spec.rb +4 -3
  210. data/spec/grape/middleware/exception_spec.rb +205 -55
  211. data/spec/grape/middleware/formatter_spec.rb +130 -15
  212. data/spec/grape/middleware/globals_spec.rb +2 -0
  213. data/spec/grape/middleware/stack_spec.rb +15 -1
  214. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -1
  215. data/spec/grape/middleware/versioner/header_spec.rb +10 -2
  216. data/spec/grape/middleware/versioner/param_spec.rb +4 -2
  217. data/spec/grape/middleware/versioner/path_spec.rb +4 -2
  218. data/spec/grape/middleware/versioner_spec.rb +2 -0
  219. data/spec/grape/named_api_spec.rb +21 -0
  220. data/spec/grape/parser_spec.rb +7 -5
  221. data/spec/grape/path_spec.rb +9 -7
  222. data/spec/grape/presenters/presenter_spec.rb +2 -0
  223. data/spec/grape/request_spec.rb +26 -0
  224. data/spec/grape/util/inheritable_setting_spec.rb +4 -2
  225. data/spec/grape/util/inheritable_values_spec.rb +2 -0
  226. data/spec/grape/util/reverse_stackable_values_spec.rb +15 -13
  227. data/spec/grape/util/stackable_values_spec.rb +16 -14
  228. data/spec/grape/util/strict_hash_configuration_spec.rb +3 -1
  229. data/spec/grape/validations/attributes_iterator_spec.rb +2 -0
  230. data/spec/grape/validations/instance_behaivour_spec.rb +5 -3
  231. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +41 -0
  232. data/spec/grape/validations/params_scope_spec.rb +364 -7
  233. data/spec/grape/validations/single_attribute_iterator_spec.rb +58 -0
  234. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  235. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  236. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  237. data/spec/grape/validations/types_spec.rb +9 -36
  238. data/spec/grape/validations/validators/all_or_none_spec.rb +140 -30
  239. data/spec/grape/validations/validators/allow_blank_spec.rb +2 -0
  240. data/spec/grape/validations/validators/at_least_one_of_spec.rb +175 -29
  241. data/spec/grape/validations/validators/coerce_spec.rb +558 -133
  242. data/spec/grape/validations/validators/default_spec.rb +212 -0
  243. data/spec/grape/validations/validators/exactly_one_of_spec.rb +204 -38
  244. data/spec/grape/validations/validators/except_values_spec.rb +6 -3
  245. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +186 -27
  246. data/spec/grape/validations/validators/presence_spec.rb +30 -0
  247. data/spec/grape/validations/validators/regexp_spec.rb +2 -0
  248. data/spec/grape/validations/validators/same_as_spec.rb +65 -0
  249. data/spec/grape/validations/validators/values_spec.rb +73 -19
  250. data/spec/grape/validations_spec.rb +403 -53
  251. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  252. data/spec/integration/multi_json/json_spec.rb +2 -0
  253. data/spec/integration/multi_xml/xml_spec.rb +2 -0
  254. data/spec/shared/versioning_examples.rb +78 -18
  255. data/spec/spec_helper.rb +13 -2
  256. data/spec/support/basic_auth_encode_helpers.rb +3 -1
  257. data/spec/support/chunks.rb +14 -0
  258. data/spec/support/content_type_helpers.rb +3 -1
  259. data/spec/support/eager_load.rb +19 -0
  260. data/spec/support/endpoint_faker.rb +2 -0
  261. data/spec/support/file_streamer.rb +2 -0
  262. data/spec/support/integer_helpers.rb +2 -0
  263. data/spec/support/versioned_helpers.rb +8 -8
  264. metadata +176 -130
  265. data/Appraisals +0 -32
  266. data/Dangerfile +0 -1
  267. data/Gemfile +0 -34
  268. data/Gemfile.lock +0 -229
  269. data/Guardfile +0 -10
  270. data/RELEASING.md +0 -111
  271. data/Rakefile +0 -70
  272. data/benchmark/simple.rb +0 -27
  273. data/benchmark/simple_with_type_coercer.rb +0 -22
  274. data/gemfiles/multi_json.gemfile +0 -36
  275. data/gemfiles/multi_xml.gemfile +0 -36
  276. data/gemfiles/rack_1.5.2.gemfile +0 -36
  277. data/gemfiles/rack_edge.gemfile +0 -36
  278. data/gemfiles/rails_3.gemfile +0 -37
  279. data/gemfiles/rails_4.gemfile +0 -36
  280. data/gemfiles/rails_5.gemfile +0 -36
  281. data/gemfiles/rails_edge.gemfile +0 -36
  282. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
  283. data/lib/grape/util/content_types.rb +0 -26
  284. data/lib/grape/validations/types/virtus_collection_patch.rb +0 -16
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Middleware::Error do
@@ -11,6 +13,15 @@ describe Grape::Middleware::Error do
11
13
  end
12
14
  end
13
15
 
16
+ # raises a non-StandardError (ScriptError) exception
17
+ class OtherExceptionApp
18
+ class << self
19
+ def call(_env)
20
+ raise NotImplementedError, 'snow!'
21
+ end
22
+ end
23
+ end
24
+
14
25
  # raises a hash error
15
26
  class ErrorHashApp
16
27
  class << self
@@ -44,153 +55,292 @@ describe Grape::Middleware::Error do
44
55
  class CustomErrorApp
45
56
  class << self
46
57
  def call(_env)
47
- raise CustomError, status: 400, message: 'failed validation'
58
+ raise CustomError.new(status: 400, message: 'failed validation')
48
59
  end
49
60
  end
50
61
  end
51
62
  end
52
63
 
53
- attr_reader :app
54
-
55
- it 'does not trap errors by default' do
56
- @app ||= Rack::Builder.app do
57
- use Spec::Support::EndpointFaker
58
- use Grape::Middleware::Error
59
- run ExceptionSpec::ExceptionApp
60
- end
61
- expect { get '/' }.to raise_error(RuntimeError, 'rain!')
64
+ def app
65
+ subject
62
66
  end
63
67
 
64
- context 'with rescue_all set to true' do
65
- it 'sets the message appropriately' do
66
- @app ||= Rack::Builder.app do
68
+ context 'with defaults' do
69
+ subject do
70
+ Rack::Builder.app do
67
71
  use Spec::Support::EndpointFaker
68
- use Grape::Middleware::Error, rescue_all: true
72
+ use Grape::Middleware::Error
69
73
  run ExceptionSpec::ExceptionApp
70
74
  end
71
- get '/'
72
- expect(last_response.body).to eq('rain!')
73
75
  end
76
+ it 'does not trap errors by default' do
77
+ expect { get '/' }.to raise_error(RuntimeError, 'rain!')
78
+ end
79
+ end
74
80
 
75
- it 'defaults to a 500 status' do
76
- @app ||= Rack::Builder.app do
77
- use Spec::Support::EndpointFaker
78
- use Grape::Middleware::Error, rescue_all: true
79
- run ExceptionSpec::ExceptionApp
81
+ context 'with rescue_all' do
82
+ context 'StandardError exception' do
83
+ subject do
84
+ Rack::Builder.app do
85
+ use Spec::Support::EndpointFaker
86
+ use Grape::Middleware::Error, rescue_all: true
87
+ run ExceptionSpec::ExceptionApp
88
+ end
89
+ end
90
+ it 'sets the message appropriately' do
91
+ get '/'
92
+ expect(last_response.body).to eq('rain!')
93
+ end
94
+ it 'defaults to a 500 status' do
95
+ get '/'
96
+ expect(last_response.status).to eq(500)
80
97
  end
81
- get '/'
82
- expect(last_response.status).to eq(500)
83
98
  end
84
99
 
85
- it 'is possible to specify a different default status code' do
86
- @app ||= Rack::Builder.app do
100
+ context 'Non-StandardError exception' do
101
+ subject do
102
+ Rack::Builder.app do
103
+ use Spec::Support::EndpointFaker
104
+ use Grape::Middleware::Error, rescue_all: true
105
+ run ExceptionSpec::OtherExceptionApp
106
+ end
107
+ end
108
+ it 'does not trap errors other than StandardError' do
109
+ expect { get '/' }.to raise_error(NotImplementedError, 'snow!')
110
+ end
111
+ end
112
+ end
113
+
114
+ context 'Non-StandardError exception with a provided rescue handler' do
115
+ context 'default error response' do
116
+ subject do
117
+ Rack::Builder.app do
118
+ use Spec::Support::EndpointFaker
119
+ use Grape::Middleware::Error, rescue_handlers: { NotImplementedError => nil }
120
+ run ExceptionSpec::OtherExceptionApp
121
+ end
122
+ end
123
+ it 'rescues the exception using the default handler' do
124
+ get '/'
125
+ expect(last_response.body).to eq('snow!')
126
+ end
127
+ end
128
+
129
+ context 'custom error response' do
130
+ subject do
131
+ Rack::Builder.app do
132
+ use Spec::Support::EndpointFaker
133
+ use Grape::Middleware::Error, rescue_handlers: { NotImplementedError => -> { Rack::Response.new('rescued', 200, {}) } }
134
+ run ExceptionSpec::OtherExceptionApp
135
+ end
136
+ end
137
+ it 'rescues the exception using the provided handler' do
138
+ get '/'
139
+ expect(last_response.body).to eq('rescued')
140
+ end
141
+ end
142
+ end
143
+
144
+ context do
145
+ subject do
146
+ Rack::Builder.app do
87
147
  use Spec::Support::EndpointFaker
88
148
  use Grape::Middleware::Error, rescue_all: true, default_status: 500
89
149
  run ExceptionSpec::ExceptionApp
90
150
  end
151
+ end
152
+ it 'is possible to specify a different default status code' do
91
153
  get '/'
92
154
  expect(last_response.status).to eq(500)
93
155
  end
156
+ end
94
157
 
95
- it 'is possible to return errors in json format' do
96
- @app ||= Rack::Builder.app do
158
+ context do
159
+ subject do
160
+ Rack::Builder.app do
97
161
  use Spec::Support::EndpointFaker
98
162
  use Grape::Middleware::Error, rescue_all: true, format: :json
99
163
  run ExceptionSpec::ExceptionApp
100
164
  end
165
+ end
166
+ it 'is possible to return errors in json format' do
101
167
  get '/'
102
168
  expect(last_response.body).to eq('{"error":"rain!"}')
103
169
  end
170
+ end
104
171
 
105
- it 'is possible to return hash errors in json format' do
106
- @app ||= Rack::Builder.app do
172
+ context do
173
+ subject do
174
+ Rack::Builder.app do
107
175
  use Spec::Support::EndpointFaker
108
176
  use Grape::Middleware::Error, rescue_all: true, format: :json
109
177
  run ExceptionSpec::ErrorHashApp
110
178
  end
179
+ end
180
+ it 'is possible to return hash errors in json format' do
111
181
  get '/'
112
182
  expect(['{"error":"rain!","detail":"missing widget"}',
113
183
  '{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
114
184
  end
185
+ end
115
186
 
116
- it 'is possible to return errors in jsonapi format' do
117
- @app ||= Rack::Builder.app do
187
+ context do
188
+ subject do
189
+ Rack::Builder.app do
118
190
  use Spec::Support::EndpointFaker
119
191
  use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
120
192
  run ExceptionSpec::ExceptionApp
121
193
  end
194
+ end
195
+ it 'is possible to return errors in jsonapi format' do
122
196
  get '/'
123
- expect(last_response.body).to eq('{"error":"rain!"}')
197
+ expect(last_response.body).to eq('{&quot;error&quot;:&quot;rain!&quot;}')
124
198
  end
199
+ end
125
200
 
126
- it 'is possible to return hash errors in jsonapi format' do
127
- @app ||= Rack::Builder.app do
201
+ context do
202
+ subject do
203
+ Rack::Builder.app do
128
204
  use Spec::Support::EndpointFaker
129
205
  use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
130
206
  run ExceptionSpec::ErrorHashApp
131
207
  end
208
+ end
209
+
210
+ it 'is possible to return hash errors in jsonapi format' do
132
211
  get '/'
133
- expect(['{"error":"rain!","detail":"missing widget"}',
134
- '{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
212
+ expect(['{&quot;error&quot;:&quot;rain!&quot;,&quot;detail&quot;:&quot;missing widget&quot;}',
213
+ '{&quot;detail&quot;:&quot;missing widget&quot;,&quot;error&quot;:&quot;rain!&quot;}']).to include(last_response.body)
135
214
  end
215
+ end
136
216
 
137
- it 'is possible to return errors in xml format' do
138
- @app ||= Rack::Builder.app do
217
+ context do
218
+ subject do
219
+ Rack::Builder.app do
139
220
  use Spec::Support::EndpointFaker
140
221
  use Grape::Middleware::Error, rescue_all: true, format: :xml
141
222
  run ExceptionSpec::ExceptionApp
142
223
  end
224
+ end
225
+ it 'is possible to return errors in xml format' do
143
226
  get '/'
144
227
  expect(last_response.body).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <message>rain!</message>\n</error>\n")
145
228
  end
229
+ end
146
230
 
147
- it 'is possible to return hash errors in xml format' do
148
- @app ||= Rack::Builder.app do
231
+ context do
232
+ subject do
233
+ Rack::Builder.app do
149
234
  use Spec::Support::EndpointFaker
150
235
  use Grape::Middleware::Error, rescue_all: true, format: :xml
151
236
  run ExceptionSpec::ErrorHashApp
152
237
  end
238
+ end
239
+ it 'is possible to return hash errors in xml format' do
153
240
  get '/'
154
241
  expect(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <detail>missing widget</detail>\n <error>rain!</error>\n</error>\n",
155
242
  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <error>rain!</error>\n <detail>missing widget</detail>\n</error>\n"]).to include(last_response.body)
156
243
  end
244
+ end
157
245
 
158
- it 'is possible to specify a custom formatter' do
159
- @app ||= Rack::Builder.app do
246
+ context do
247
+ subject do
248
+ Rack::Builder.app do
160
249
  use Spec::Support::EndpointFaker
161
- use Grape::Middleware::Error, rescue_all: true,
162
- format: :custom,
163
- error_formatters: {
164
- custom: lambda do |message, _backtrace, _options, _env|
165
- { custom_formatter: message }.inspect
166
- end
167
- }
250
+ use Grape::Middleware::Error,
251
+ rescue_all: true,
252
+ format: :custom,
253
+ error_formatters: {
254
+ custom: lambda do |message, _backtrace, _options, _env, _original_exception|
255
+ { custom_formatter: message }.inspect
256
+ end
257
+ }
168
258
  run ExceptionSpec::ExceptionApp
169
259
  end
260
+ end
261
+ it 'is possible to specify a custom formatter' do
170
262
  get '/'
171
- expect(last_response.body).to eq('{:custom_formatter=>"rain!"}')
263
+ expect(last_response.body).to eq('{:custom_formatter=&gt;&quot;rain!&quot;}')
172
264
  end
265
+ end
173
266
 
174
- it 'does not trap regular error! codes' do
175
- @app ||= Rack::Builder.app do
267
+ context do
268
+ subject do
269
+ Rack::Builder.app do
176
270
  use Spec::Support::EndpointFaker
177
271
  use Grape::Middleware::Error
178
272
  run ExceptionSpec::AccessDeniedApp
179
273
  end
274
+ end
275
+ it 'does not trap regular error! codes' do
180
276
  get '/'
181
277
  expect(last_response.status).to eq(401)
182
278
  end
279
+ end
183
280
 
184
- it 'responds to custom Grape exceptions appropriately' do
185
- @app ||= Rack::Builder.app do
281
+ context do
282
+ subject do
283
+ Rack::Builder.app do
186
284
  use Spec::Support::EndpointFaker
187
285
  use Grape::Middleware::Error, rescue_all: false
188
286
  run ExceptionSpec::CustomErrorApp
189
287
  end
190
-
288
+ end
289
+ it 'responds to custom Grape exceptions appropriately' do
191
290
  get '/'
192
291
  expect(last_response.status).to eq(400)
193
292
  expect(last_response.body).to eq('failed validation')
194
293
  end
195
294
  end
295
+
296
+ context 'with rescue_options :backtrace and :exception set to true' do
297
+ subject do
298
+ Rack::Builder.app do
299
+ use Spec::Support::EndpointFaker
300
+ use Grape::Middleware::Error,
301
+ rescue_all: true,
302
+ format: :json,
303
+ rescue_options: { backtrace: true, original_exception: true }
304
+ run ExceptionSpec::ExceptionApp
305
+ end
306
+ end
307
+ it 'is possible to return the backtrace and the original exception in json format' do
308
+ get '/'
309
+ expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original_exception', 'RuntimeError')
310
+ end
311
+ end
312
+
313
+ context do
314
+ subject do
315
+ Rack::Builder.app do
316
+ use Spec::Support::EndpointFaker
317
+ use Grape::Middleware::Error,
318
+ rescue_all: true,
319
+ format: :xml,
320
+ rescue_options: { backtrace: true, original_exception: true }
321
+ run ExceptionSpec::ExceptionApp
322
+ end
323
+ end
324
+ it 'is possible to return the backtrace and the original exception in xml format' do
325
+ get '/'
326
+ expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original-exception', 'RuntimeError')
327
+ end
328
+ end
329
+
330
+ context do
331
+ subject do
332
+ Rack::Builder.app do
333
+ use Spec::Support::EndpointFaker
334
+ use Grape::Middleware::Error,
335
+ rescue_all: true,
336
+ format: :txt,
337
+ rescue_options: { backtrace: true, original_exception: true }
338
+ run ExceptionSpec::ExceptionApp
339
+ end
340
+ end
341
+ it 'is possible to return the backtrace and the original exception in txt format' do
342
+ get '/'
343
+ expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original exception', 'RuntimeError')
344
+ end
345
+ end
196
346
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Middleware::Formatter do
@@ -18,7 +20,7 @@ describe Grape::Middleware::Formatter do
18
20
  let(:body) { ['foo'] }
19
21
  it 'calls #to_json since default format is json' do
20
22
  body.instance_eval do
21
- def to_json
23
+ def to_json(*_args)
22
24
  '"bar"'
23
25
  end
24
26
  end
@@ -31,7 +33,7 @@ describe Grape::Middleware::Formatter do
31
33
  let(:body) { { 'foos' => [{ 'bar' => 'baz' }] } }
32
34
  it 'calls #to_json if the content type is jsonapi' do
33
35
  body.instance_eval do
34
- def to_json
36
+ def to_json(*_args)
35
37
  '{"foos":[{"bar":"baz"}] }'
36
38
  end
37
39
  end
@@ -41,7 +43,7 @@ describe Grape::Middleware::Formatter do
41
43
  end
42
44
 
43
45
  context 'xml' do
44
- let(:body) { 'string' }
46
+ let(:body) { +'string' }
45
47
  it 'calls #to_xml if the content type is xml' do
46
48
  body.instance_eval do
47
49
  def to_xml
@@ -194,26 +196,32 @@ describe Grape::Middleware::Formatter do
194
196
  subject.options[:content_types][:custom] = "don't care"
195
197
  subject.options[:formatters][:custom] = ->(_obj, _env) { 'CUSTOM FORMAT' }
196
198
  _, _, body = subject.call('PATH_INFO' => '/info.custom')
197
- expect(body.body).to eq(['CUSTOM FORMAT'])
199
+ expect(read_chunks(body)).to eq(['CUSTOM FORMAT'])
198
200
  end
199
201
  context 'default' do
200
202
  let(:body) { ['blah'] }
201
203
  it 'uses default json formatter' do
202
204
  _, _, body = subject.call('PATH_INFO' => '/info.json')
203
- expect(body.body).to eq(['["blah"]'])
205
+ expect(read_chunks(body)).to eq(['["blah"]'])
204
206
  end
205
207
  end
206
208
  it 'uses custom json formatter' do
207
209
  subject.options[:formatters][:json] = ->(_obj, _env) { 'CUSTOM JSON FORMAT' }
208
210
  _, _, body = subject.call('PATH_INFO' => '/info.json')
209
- expect(body.body).to eq(['CUSTOM JSON FORMAT'])
211
+ expect(read_chunks(body)).to eq(['CUSTOM JSON FORMAT'])
210
212
  end
211
213
  end
212
214
 
213
215
  context 'no content responses' do
214
216
  let(:no_content_response) { ->(status) { [status, {}, ['']] } }
215
217
 
216
- Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.each do |status|
218
+ STATUSES_WITHOUT_BODY = if Gem::Version.new(Rack.release) >= Gem::Version.new('2.1.0')
219
+ Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.keys
220
+ else
221
+ Rack::Utils::STATUS_WITH_NO_ENTITY_BODY
222
+ end
223
+
224
+ STATUSES_WITHOUT_BODY.each do |status|
217
225
  it "does not modify a #{status} response" do
218
226
  expected_response = no_content_response[status]
219
227
  allow(app).to receive(:call).and_return(expected_response)
@@ -223,7 +231,81 @@ describe Grape::Middleware::Formatter do
223
231
  end
224
232
 
225
233
  context 'input' do
226
- %w(POST PATCH PUT DELETE).each do |method|
234
+ %w[POST PATCH PUT DELETE].each do |method|
235
+ context 'when body is not nil or empty' do
236
+ context 'when Content-Type is supported' do
237
+ let(:io) { StringIO.new('{"is_boolean":true,"string":"thing"}') }
238
+ let(:content_type) { 'application/json' }
239
+
240
+ it "parses the body from #{method} and copies values into rack.request.form_hash" do
241
+ subject.call(
242
+ 'PATH_INFO' => '/info',
243
+ 'REQUEST_METHOD' => method,
244
+ 'CONTENT_TYPE' => content_type,
245
+ 'rack.input' => io,
246
+ 'CONTENT_LENGTH' => io.length
247
+ )
248
+ expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
249
+ expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
250
+ end
251
+ end
252
+
253
+ context 'when Content-Type is not supported' do
254
+ let(:io) { StringIO.new('{"is_boolean":true,"string":"thing"}') }
255
+ let(:content_type) { 'application/atom+xml' }
256
+
257
+ it 'returns a 415 HTTP error status' do
258
+ error = catch(:error) do
259
+ subject.call(
260
+ 'PATH_INFO' => '/info',
261
+ 'REQUEST_METHOD' => method,
262
+ 'CONTENT_TYPE' => content_type,
263
+ 'rack.input' => io,
264
+ 'CONTENT_LENGTH' => io.length
265
+ )
266
+ end
267
+ expect(error[:status]).to eq(415)
268
+ expect(error[:message]).to eq("The provided content-type 'application/atom+xml' is not supported.")
269
+ end
270
+ end
271
+ end
272
+
273
+ context 'when body is nil' do
274
+ let(:io) { double }
275
+ before do
276
+ allow(io).to receive_message_chain(:rewind, :read).and_return(nil)
277
+ end
278
+
279
+ it 'does not read and parse the body' do
280
+ expect(subject).not_to receive(:read_rack_input)
281
+ subject.call(
282
+ 'PATH_INFO' => '/info',
283
+ 'REQUEST_METHOD' => method,
284
+ 'CONTENT_TYPE' => 'application/json',
285
+ 'rack.input' => io,
286
+ 'CONTENT_LENGTH' => 0
287
+ )
288
+ end
289
+ end
290
+
291
+ context 'when body is empty' do
292
+ let(:io) { double }
293
+ before do
294
+ allow(io).to receive_message_chain(:rewind, :read).and_return('')
295
+ end
296
+
297
+ it 'does not read and parse the body' do
298
+ expect(subject).not_to receive(:read_rack_input)
299
+ subject.call(
300
+ 'PATH_INFO' => '/info',
301
+ 'REQUEST_METHOD' => method,
302
+ 'CONTENT_TYPE' => 'application/json',
303
+ 'rack.input' => io,
304
+ 'CONTENT_LENGTH' => 0
305
+ )
306
+ end
307
+ end
308
+
227
309
  ['application/json', 'application/json; charset=utf-8'].each do |content_type|
228
310
  context content_type do
229
311
  it "parses the body from #{method} and copies values into rack.request.form_hash" do
@@ -297,12 +379,17 @@ describe Grape::Middleware::Formatter do
297
379
  end
298
380
 
299
381
  context 'send file' do
300
- let(:body) { Grape::ServeFile::FileResponse.new('file') }
301
- let(:app) { ->(_env) { [200, {}, body] } }
382
+ let(:file) { double(File) }
383
+ let(:file_body) { Grape::ServeStream::StreamResponse.new(file) }
384
+ let(:app) { ->(_env) { [200, {}, file_body] } }
302
385
 
303
- it 'returns Grape::Uril::SendFileReponse' do
386
+ it 'returns a file response' do
387
+ expect(file).to receive(:each).and_yield('data')
304
388
  env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' }
305
- expect(subject.call(env)).to be_a(Grape::ServeFile::SendfileResponse)
389
+ status, headers, body = subject.call(env)
390
+ expect(status).to be == 200
391
+ expect(headers).to be == { 'Content-Type' => 'application/json' }
392
+ expect(read_chunks(body)).to be == ['data']
306
393
  end
307
394
  end
308
395
 
@@ -315,13 +402,41 @@ describe Grape::Middleware::Formatter do
315
402
  let(:app) { ->(_env) { [200, {}, ['']] } }
316
403
  before do
317
404
  Grape::Formatter.register :invalid, InvalidFormatter
318
- Grape::ContentTypes::CONTENT_TYPES[:invalid] = 'application/x-invalid'
405
+ Grape::ContentTypes.register :invalid, 'application/x-invalid'
406
+ end
407
+ after do
408
+ Grape::ContentTypes.default_elements.delete(:invalid)
409
+ Grape::Formatter.default_elements.delete(:invalid)
319
410
  end
320
411
 
321
412
  it 'returns response by invalid formatter' do
322
413
  env = { 'PATH_INFO' => '/hello.invalid', 'HTTP_ACCEPT' => 'application/x-invalid' }
323
- _, _, bodies = *subject.call(env)
324
- expect(bodies.body.first).to eq({ message: 'invalid' }.to_json)
414
+ _, _, body = *subject.call(env)
415
+ expect(read_chunks(body).join).to eq({ message: 'invalid' }.to_json)
416
+ end
417
+ end
418
+
419
+ context 'custom parser raises exception and rescue options are enabled for backtrace and original_exception' do
420
+ it 'adds the backtrace and original_exception to the error output' do
421
+ subject = Grape::Middleware::Formatter.new(
422
+ app,
423
+ rescue_options: { backtrace: true, original_exception: true },
424
+ parsers: { json: ->(_object, _env) { raise StandardError, 'fail' } }
425
+ )
426
+ io = StringIO.new('{invalid}')
427
+ error = catch(:error) do
428
+ subject.call(
429
+ 'PATH_INFO' => '/info',
430
+ 'REQUEST_METHOD' => 'POST',
431
+ 'CONTENT_TYPE' => 'application/json',
432
+ 'rack.input' => io,
433
+ 'CONTENT_LENGTH' => io.length
434
+ )
435
+ end
436
+
437
+ expect(error[:message]).to eq 'fail'
438
+ expect(error[:backtrace].size).to be >= 1
439
+ expect(error[:original_exception].class).to eq StandardError
325
440
  end
326
441
  end
327
442
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Middleware::Globals do
@@ -1,18 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Middleware::Stack do
4
6
  module StackSpec
5
7
  class FooMiddleware; end
8
+
6
9
  class BarMiddleware; end
10
+
7
11
  class BlockMiddleware
8
12
  attr_reader :block
13
+
9
14
  def initialize(&block)
10
15
  @block = block
11
16
  end
12
17
  end
13
18
  end
14
19
 
15
- let(:proc) { ->() {} }
20
+ let(:proc) { -> {} }
16
21
  let(:others) { [[:use, StackSpec::BarMiddleware], [:insert_before, StackSpec::BarMiddleware, StackSpec::BlockMiddleware, proc]] }
17
22
 
18
23
  subject { Grape::Middleware::Stack.new }
@@ -109,6 +114,15 @@ describe Grape::Middleware::Stack do
109
114
  expect(subject[1]).to eq(StackSpec::BlockMiddleware)
110
115
  expect(subject[2]).to eq(StackSpec::BarMiddleware)
111
116
  end
117
+
118
+ context 'middleware spec with proc declaration exists' do
119
+ let(:middleware_spec_with_proc) { [:use, StackSpec::FooMiddleware, proc] }
120
+
121
+ it 'properly forwards spec arguments' do
122
+ expect(subject).to receive(:use).with(StackSpec::FooMiddleware)
123
+ subject.merge_with([middleware_spec_with_proc])
124
+ end
125
+ end
112
126
  end
113
127
 
114
128
  describe '#build' do
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Middleware::Versioner::AcceptVersionHeader do
4
6
  let(:app) { ->(env) { [200, env, env] } }
5
- subject { Grape::Middleware::Versioner::AcceptVersionHeader.new(app, @options || {}) }
7
+ subject { Grape::Middleware::Versioner::AcceptVersionHeader.new(app, **(@options || {})) }
6
8
 
7
9
  before do
8
10
  @options = {
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Middleware::Versioner::Header do
4
6
  let(:app) { ->(env) { [200, env, env] } }
5
- subject { Grape::Middleware::Versioner::Header.new(app, @options || {}) }
7
+ subject { Grape::Middleware::Versioner::Header.new(app, **(@options || {})) }
6
8
 
7
9
  before do
8
10
  @options = {
@@ -160,6 +162,12 @@ describe Grape::Middleware::Versioner::Header do
160
162
  expect(subject.call({}).first).to eq(200)
161
163
  end
162
164
 
165
+ it 'succeeds if :strict is set to false and given an invalid header' do
166
+ @options[:version_options][:strict] = false
167
+ expect(subject.call('HTTP_ACCEPT' => 'yaml').first).to eq(200)
168
+ expect(subject.call({}).first).to eq(200)
169
+ end
170
+
163
171
  context 'when :strict is set' do
164
172
  before do
165
173
  @options[:versions] = ['v1']
@@ -241,7 +249,7 @@ describe Grape::Middleware::Versioner::Header do
241
249
 
242
250
  context 'when multiple versions are specified' do
243
251
  before do
244
- @options[:versions] = %w(v1 v2)
252
+ @options[:versions] = %w[v1 v2]
245
253
  end
246
254
 
247
255
  it 'succeeds with v1' do