grape 1.5.2 → 1.7.0

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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -0
  3. data/CONTRIBUTING.md +2 -1
  4. data/README.md +152 -21
  5. data/UPGRADING.md +86 -2
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +14 -18
  8. data/lib/grape/api.rb +18 -13
  9. data/lib/grape/cookies.rb +2 -0
  10. data/lib/grape/dry_types.rb +12 -0
  11. data/lib/grape/dsl/api.rb +0 -2
  12. data/lib/grape/dsl/callbacks.rb +0 -2
  13. data/lib/grape/dsl/configuration.rb +0 -2
  14. data/lib/grape/dsl/desc.rb +2 -19
  15. data/lib/grape/dsl/headers.rb +5 -2
  16. data/lib/grape/dsl/helpers.rb +7 -7
  17. data/lib/grape/dsl/inside_route.rb +43 -30
  18. data/lib/grape/dsl/middleware.rb +4 -6
  19. data/lib/grape/dsl/parameters.rb +8 -10
  20. data/lib/grape/dsl/request_response.rb +9 -8
  21. data/lib/grape/dsl/routing.rb +6 -4
  22. data/lib/grape/dsl/settings.rb +5 -7
  23. data/lib/grape/dsl/validations.rb +0 -15
  24. data/lib/grape/endpoint.rb +21 -36
  25. data/lib/grape/error_formatter/json.rb +9 -7
  26. data/lib/grape/error_formatter/xml.rb +2 -6
  27. data/lib/grape/exceptions/base.rb +2 -2
  28. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  29. data/lib/grape/exceptions/missing_group_type.rb +8 -1
  30. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  31. data/lib/grape/exceptions/unsupported_group_type.rb +8 -1
  32. data/lib/grape/exceptions/validation.rb +1 -6
  33. data/lib/grape/formatter/json.rb +1 -0
  34. data/lib/grape/formatter/serializable_hash.rb +2 -1
  35. data/lib/grape/formatter/xml.rb +1 -0
  36. data/lib/grape/locale/en.yml +9 -8
  37. data/lib/grape/middleware/auth/dsl.rb +7 -2
  38. data/lib/grape/middleware/base.rb +3 -1
  39. data/lib/grape/middleware/error.rb +2 -2
  40. data/lib/grape/middleware/formatter.rb +4 -4
  41. data/lib/grape/middleware/stack.rb +2 -2
  42. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  43. data/lib/grape/middleware/versioner/header.rb +6 -4
  44. data/lib/grape/middleware/versioner/param.rb +1 -0
  45. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  46. data/lib/grape/middleware/versioner/path.rb +2 -0
  47. data/lib/grape/parser/json.rb +1 -1
  48. data/lib/grape/parser/xml.rb +1 -1
  49. data/lib/grape/path.rb +1 -0
  50. data/lib/grape/request.rb +5 -0
  51. data/lib/grape/router/pattern.rb +1 -1
  52. data/lib/grape/router/route.rb +2 -2
  53. data/lib/grape/router.rb +6 -0
  54. data/lib/grape/util/inheritable_setting.rb +1 -3
  55. data/lib/grape/util/json.rb +2 -0
  56. data/lib/grape/util/lazy_value.rb +3 -2
  57. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  58. data/lib/grape/validations/attributes_doc.rb +58 -0
  59. data/lib/grape/validations/params_scope.rb +137 -78
  60. data/lib/grape/validations/types/array_coercer.rb +0 -2
  61. data/lib/grape/validations/types/custom_type_coercer.rb +1 -2
  62. data/lib/grape/validations/types/dry_type_coercer.rb +4 -8
  63. data/lib/grape/validations/types/json.rb +2 -1
  64. data/lib/grape/validations/types/primitive_coercer.rb +16 -8
  65. data/lib/grape/validations/types/set_coercer.rb +0 -2
  66. data/lib/grape/validations/types.rb +98 -30
  67. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  68. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  69. data/lib/grape/validations/validators/as_validator.rb +14 -0
  70. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  71. data/lib/grape/validations/validators/base.rb +82 -70
  72. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  73. data/lib/grape/validations/validators/default_validator.rb +51 -0
  74. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  75. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  76. data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
  77. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  78. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  79. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  80. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  81. data/lib/grape/validations/validators/values_validator.rb +88 -0
  82. data/lib/grape/validations.rb +16 -6
  83. data/lib/grape/version.rb +1 -1
  84. data/lib/grape.rb +70 -29
  85. data/spec/grape/api/custom_validations_spec.rb +116 -45
  86. data/spec/grape/api/deeply_included_options_spec.rb +3 -5
  87. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -3
  88. data/spec/grape/api/documentation_spec.rb +59 -0
  89. data/spec/grape/api/inherited_helpers_spec.rb +0 -2
  90. data/spec/grape/api/instance_spec.rb +0 -1
  91. data/spec/grape/api/invalid_format_spec.rb +2 -2
  92. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -2
  93. data/spec/grape/api/nested_helpers_spec.rb +0 -2
  94. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -2
  95. data/spec/grape/api/parameters_modification_spec.rb +0 -2
  96. data/spec/grape/api/patch_method_helpers_spec.rb +0 -2
  97. data/spec/grape/api/recognize_path_spec.rb +1 -3
  98. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -2
  99. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -2
  100. data/spec/grape/api/routes_with_requirements_spec.rb +8 -10
  101. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -17
  102. data/spec/grape/api/shared_helpers_spec.rb +0 -2
  103. data/spec/grape/api_remount_spec.rb +16 -16
  104. data/spec/grape/api_spec.rb +527 -224
  105. data/spec/grape/config_spec.rb +0 -2
  106. data/spec/grape/dsl/callbacks_spec.rb +2 -3
  107. data/spec/grape/dsl/configuration_spec.rb +0 -2
  108. data/spec/grape/dsl/desc_spec.rb +0 -2
  109. data/spec/grape/dsl/headers_spec.rb +39 -11
  110. data/spec/grape/dsl/helpers_spec.rb +3 -4
  111. data/spec/grape/dsl/inside_route_spec.rb +16 -16
  112. data/spec/grape/dsl/logger_spec.rb +15 -19
  113. data/spec/grape/dsl/middleware_spec.rb +2 -3
  114. data/spec/grape/dsl/parameters_spec.rb +2 -2
  115. data/spec/grape/dsl/request_response_spec.rb +7 -8
  116. data/spec/grape/dsl/routing_spec.rb +11 -10
  117. data/spec/grape/dsl/settings_spec.rb +0 -2
  118. data/spec/grape/dsl/validations_spec.rb +0 -17
  119. data/spec/grape/endpoint/declared_spec.rb +261 -16
  120. data/spec/grape/endpoint_spec.rb +98 -57
  121. data/spec/grape/entity_spec.rb +22 -23
  122. data/spec/grape/exceptions/base_spec.rb +16 -2
  123. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -2
  124. data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -24
  125. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -2
  126. data/spec/grape/exceptions/invalid_response_spec.rb +0 -2
  127. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +1 -3
  128. data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
  129. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -2
  130. data/spec/grape/exceptions/missing_option_spec.rb +1 -3
  131. data/spec/grape/exceptions/unknown_options_spec.rb +0 -2
  132. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -2
  133. data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
  134. data/spec/grape/exceptions/validation_errors_spec.rb +13 -11
  135. data/spec/grape/exceptions/validation_spec.rb +5 -5
  136. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -9
  137. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -10
  138. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -10
  139. data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
  140. data/spec/grape/integration/rack_sendfile_spec.rb +1 -3
  141. data/spec/grape/integration/rack_spec.rb +0 -2
  142. data/spec/grape/loading_spec.rb +8 -10
  143. data/spec/grape/middleware/auth/base_spec.rb +0 -1
  144. data/spec/grape/middleware/auth/dsl_spec.rb +15 -8
  145. data/spec/grape/middleware/auth/strategies_spec.rb +60 -22
  146. data/spec/grape/middleware/base_spec.rb +24 -17
  147. data/spec/grape/middleware/error_spec.rb +8 -3
  148. data/spec/grape/middleware/exception_spec.rb +111 -163
  149. data/spec/grape/middleware/formatter_spec.rb +27 -8
  150. data/spec/grape/middleware/globals_spec.rb +7 -6
  151. data/spec/grape/middleware/stack_spec.rb +14 -14
  152. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -3
  153. data/spec/grape/middleware/versioner/header_spec.rb +30 -15
  154. data/spec/grape/middleware/versioner/param_spec.rb +7 -3
  155. data/spec/grape/middleware/versioner/path_spec.rb +5 -3
  156. data/spec/grape/middleware/versioner_spec.rb +1 -3
  157. data/spec/grape/named_api_spec.rb +0 -2
  158. data/spec/grape/parser_spec.rb +4 -2
  159. data/spec/grape/path_spec.rb +52 -54
  160. data/spec/grape/presenters/presenter_spec.rb +7 -8
  161. data/spec/grape/request_spec.rb +6 -6
  162. data/spec/grape/util/inheritable_setting_spec.rb +7 -8
  163. data/spec/grape/util/inheritable_values_spec.rb +3 -3
  164. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -2
  165. data/spec/grape/util/stackable_values_spec.rb +7 -6
  166. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -1
  167. data/spec/grape/validations/attributes_doc_spec.rb +153 -0
  168. data/spec/grape/validations/attributes_iterator_spec.rb +0 -2
  169. data/spec/grape/validations/instance_behaivour_spec.rb +9 -12
  170. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -2
  171. data/spec/grape/validations/params_scope_spec.rb +361 -96
  172. data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -3
  173. data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
  174. data/spec/grape/validations/types/primitive_coercer_spec.rb +24 -9
  175. data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
  176. data/spec/grape/validations/types_spec.rb +36 -10
  177. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -58
  178. data/spec/grape/validations/validators/allow_blank_spec.rb +135 -141
  179. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -58
  180. data/spec/grape/validations/validators/coerce_spec.rb +99 -24
  181. data/spec/grape/validations/validators/default_spec.rb +72 -80
  182. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -79
  183. data/spec/grape/validations/validators/except_values_spec.rb +3 -5
  184. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -79
  185. data/spec/grape/validations/validators/presence_spec.rb +16 -3
  186. data/spec/grape/validations/validators/regexp_spec.rb +25 -33
  187. data/spec/grape/validations/validators/same_as_spec.rb +14 -22
  188. data/spec/grape/validations/validators/values_spec.rb +182 -179
  189. data/spec/grape/validations_spec.rb +149 -80
  190. data/spec/integration/eager_load/eager_load_spec.rb +2 -2
  191. data/spec/integration/multi_json/json_spec.rb +1 -3
  192. data/spec/integration/multi_xml/xml_spec.rb +1 -3
  193. data/spec/shared/versioning_examples.rb +12 -9
  194. data/spec/spec_helper.rb +21 -6
  195. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  196. metadata +125 -115
  197. data/lib/grape/validations/validators/all_or_none.rb +0 -15
  198. data/lib/grape/validations/validators/allow_blank.rb +0 -18
  199. data/lib/grape/validations/validators/as.rb +0 -16
  200. data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
  201. data/lib/grape/validations/validators/coerce.rb +0 -91
  202. data/lib/grape/validations/validators/default.rb +0 -48
  203. data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
  204. data/lib/grape/validations/validators/except_values.rb +0 -22
  205. data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
  206. data/lib/grape/validations/validators/presence.rb +0 -12
  207. data/lib/grape/validations/validators/regexp.rb +0 -13
  208. data/lib/grape/validations/validators/same_as.rb +0 -26
  209. data/lib/grape/validations/validators/values.rb +0 -83
  210. data/spec/support/eager_load.rb +0 -19
@@ -1,78 +1,82 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe Grape::Middleware::Error do
6
- # raises a text exception
7
- module ExceptionSpec
8
- class ExceptionApp
4
+ let(:exception_app) do
5
+ Class.new do
9
6
  class << self
10
7
  def call(_env)
11
8
  raise 'rain!'
12
9
  end
13
10
  end
14
11
  end
12
+ end
15
13
 
16
- # raises a non-StandardError (ScriptError) exception
17
- class OtherExceptionApp
14
+ let(:other_exception_app) do
15
+ Class.new do
18
16
  class << self
19
17
  def call(_env)
20
18
  raise NotImplementedError, 'snow!'
21
19
  end
22
20
  end
23
21
  end
22
+ end
24
23
 
25
- # raises a hash error
26
- class ErrorHashApp
24
+ let(:custom_error_app) do
25
+ Class.new do
27
26
  class << self
28
- def error!(message, status)
29
- throw :error, message: { error: message, detail: 'missing widget' }, status: status
30
- end
27
+ class CustomError < Grape::Exceptions::Base; end
31
28
 
32
29
  def call(_env)
33
- error!('rain!', 401)
30
+ raise CustomError.new(status: 400, message: 'failed validation')
34
31
  end
35
32
  end
36
33
  end
34
+ end
37
35
 
38
- # raises an error!
39
- class AccessDeniedApp
36
+ let(:error_hash_app) do
37
+ Class.new do
40
38
  class << self
41
39
  def error!(message, status)
42
- throw :error, message: message, status: status
40
+ throw :error, message: { error: message, detail: 'missing widget' }, status: status
43
41
  end
44
42
 
45
43
  def call(_env)
46
- error!('Access Denied', 401)
44
+ error!('rain!', 401)
47
45
  end
48
46
  end
49
47
  end
48
+ end
50
49
 
51
- # raises a custom error
52
- class CustomError < Grape::Exceptions::Base
53
- end
54
-
55
- class CustomErrorApp
50
+ let(:access_denied_app) do
51
+ Class.new do
56
52
  class << self
53
+ def error!(message, status)
54
+ throw :error, message: message, status: status
55
+ end
56
+
57
57
  def call(_env)
58
- raise CustomError.new(status: 400, message: 'failed validation')
58
+ error!('Access Denied', 401)
59
59
  end
60
60
  end
61
61
  end
62
62
  end
63
63
 
64
- def app
65
- subject
64
+ let(:app) do
65
+ builder = Rack::Builder.new
66
+ builder.use Spec::Support::EndpointFaker
67
+ if options.any?
68
+ builder.use described_class, options
69
+ else
70
+ builder.use described_class
71
+ end
72
+ builder.run running_app
73
+ builder.to_app
66
74
  end
67
75
 
68
76
  context 'with defaults' do
69
- subject do
70
- Rack::Builder.app do
71
- use Spec::Support::EndpointFaker
72
- use Grape::Middleware::Error
73
- run ExceptionSpec::ExceptionApp
74
- end
75
- end
77
+ let(:running_app) { exception_app }
78
+ let(:options) { {} }
79
+
76
80
  it 'does not trap errors by default' do
77
81
  expect { get '/' }.to raise_error(RuntimeError, 'rain!')
78
82
  end
@@ -80,17 +84,14 @@ describe Grape::Middleware::Error do
80
84
 
81
85
  context 'with rescue_all' do
82
86
  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
87
+ let(:running_app) { exception_app }
88
+ let(:options) { { rescue_all: true } }
89
+
90
90
  it 'sets the message appropriately' do
91
91
  get '/'
92
92
  expect(last_response.body).to eq('rain!')
93
93
  end
94
+
94
95
  it 'defaults to a 500 status' do
95
96
  get '/'
96
97
  expect(last_response.status).to eq(500)
@@ -98,13 +99,9 @@ describe Grape::Middleware::Error do
98
99
  end
99
100
 
100
101
  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
102
+ let(:running_app) { other_exception_app }
103
+ let(:options) { { rescue_all: true } }
104
+
108
105
  it 'does not trap errors other than StandardError' do
109
106
  expect { get '/' }.to raise_error(NotImplementedError, 'snow!')
110
107
  end
@@ -113,13 +110,9 @@ describe Grape::Middleware::Error do
113
110
 
114
111
  context 'Non-StandardError exception with a provided rescue handler' do
115
112
  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
113
+ let(:running_app) { other_exception_app }
114
+ let(:options) { { rescue_handlers: { NotImplementedError => nil } } }
115
+
123
116
  it 'rescues the exception using the default handler' do
124
117
  get '/'
125
118
  expect(last_response.body).to eq('snow!')
@@ -127,13 +120,9 @@ describe Grape::Middleware::Error do
127
120
  end
128
121
 
129
122
  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
123
+ let(:running_app) { other_exception_app }
124
+ let(:options) { { rescue_handlers: { NotImplementedError => -> { Rack::Response.new('rescued', 200, {}) } } } }
125
+
137
126
  it 'rescues the exception using the provided handler' do
138
127
  get '/'
139
128
  expect(last_response.body).to eq('rescued')
@@ -142,13 +131,9 @@ describe Grape::Middleware::Error do
142
131
  end
143
132
 
144
133
  context do
145
- subject do
146
- Rack::Builder.app do
147
- use Spec::Support::EndpointFaker
148
- use Grape::Middleware::Error, rescue_all: true, default_status: 500
149
- run ExceptionSpec::ExceptionApp
150
- end
151
- end
134
+ let(:running_app) { exception_app }
135
+ let(:options) { { rescue_all: true, default_status: 500 } }
136
+
152
137
  it 'is possible to specify a different default status code' do
153
138
  get '/'
154
139
  expect(last_response.status).to eq(500)
@@ -156,13 +141,9 @@ describe Grape::Middleware::Error do
156
141
  end
157
142
 
158
143
  context do
159
- subject do
160
- Rack::Builder.app do
161
- use Spec::Support::EndpointFaker
162
- use Grape::Middleware::Error, rescue_all: true, format: :json
163
- run ExceptionSpec::ExceptionApp
164
- end
165
- end
144
+ let(:running_app) { exception_app }
145
+ let(:options) { { rescue_all: true, format: :json } }
146
+
166
147
  it 'is possible to return errors in json format' do
167
148
  get '/'
168
149
  expect(last_response.body).to eq('{"error":"rain!"}')
@@ -170,13 +151,9 @@ describe Grape::Middleware::Error do
170
151
  end
171
152
 
172
153
  context do
173
- subject do
174
- Rack::Builder.app do
175
- use Spec::Support::EndpointFaker
176
- use Grape::Middleware::Error, rescue_all: true, format: :json
177
- run ExceptionSpec::ErrorHashApp
178
- end
179
- end
154
+ let(:running_app) { error_hash_app }
155
+ let(:options) { { rescue_all: true, format: :json } }
156
+
180
157
  it 'is possible to return hash errors in json format' do
181
158
  get '/'
182
159
  expect(['{"error":"rain!","detail":"missing widget"}',
@@ -185,13 +162,9 @@ describe Grape::Middleware::Error do
185
162
  end
186
163
 
187
164
  context do
188
- subject do
189
- Rack::Builder.app do
190
- use Spec::Support::EndpointFaker
191
- use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
192
- run ExceptionSpec::ExceptionApp
193
- end
194
- end
165
+ let(:running_app) { exception_app }
166
+ let(:options) { { rescue_all: true, format: :jsonapi } }
167
+
195
168
  it 'is possible to return errors in jsonapi format' do
196
169
  get '/'
197
170
  expect(last_response.body).to eq('{&quot;error&quot;:&quot;rain!&quot;}')
@@ -199,13 +172,8 @@ describe Grape::Middleware::Error do
199
172
  end
200
173
 
201
174
  context do
202
- subject do
203
- Rack::Builder.app do
204
- use Spec::Support::EndpointFaker
205
- use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
206
- run ExceptionSpec::ErrorHashApp
207
- end
208
- end
175
+ let(:running_app) { error_hash_app }
176
+ let(:options) { { rescue_all: true, format: :jsonapi } }
209
177
 
210
178
  it 'is possible to return hash errors in jsonapi format' do
211
179
  get '/'
@@ -215,13 +183,9 @@ describe Grape::Middleware::Error do
215
183
  end
216
184
 
217
185
  context do
218
- subject do
219
- Rack::Builder.app do
220
- use Spec::Support::EndpointFaker
221
- use Grape::Middleware::Error, rescue_all: true, format: :xml
222
- run ExceptionSpec::ExceptionApp
223
- end
224
- end
186
+ let(:running_app) { exception_app }
187
+ let(:options) { { rescue_all: true, format: :xml } }
188
+
225
189
  it 'is possible to return errors in xml format' do
226
190
  get '/'
227
191
  expect(last_response.body).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <message>rain!</message>\n</error>\n")
@@ -229,13 +193,9 @@ describe Grape::Middleware::Error do
229
193
  end
230
194
 
231
195
  context do
232
- subject do
233
- Rack::Builder.app do
234
- use Spec::Support::EndpointFaker
235
- use Grape::Middleware::Error, rescue_all: true, format: :xml
236
- run ExceptionSpec::ErrorHashApp
237
- end
238
- end
196
+ let(:running_app) { error_hash_app }
197
+ let(:options) { { rescue_all: true, format: :xml } }
198
+
239
199
  it 'is possible to return hash errors in xml format' do
240
200
  get '/'
241
201
  expect(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <detail>missing widget</detail>\n <error>rain!</error>\n</error>\n",
@@ -244,20 +204,19 @@ describe Grape::Middleware::Error do
244
204
  end
245
205
 
246
206
  context do
247
- subject do
248
- Rack::Builder.app do
249
- use Spec::Support::EndpointFaker
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
- }
258
- run ExceptionSpec::ExceptionApp
259
- end
207
+ let(:running_app) { exception_app }
208
+ let(:options) do
209
+ {
210
+ rescue_all: true,
211
+ format: :custom,
212
+ error_formatters: {
213
+ custom: lambda do |message, _backtrace, _options, _env, _original_exception|
214
+ { custom_formatter: message }.inspect
215
+ end
216
+ }
217
+ }
260
218
  end
219
+
261
220
  it 'is possible to specify a custom formatter' do
262
221
  get '/'
263
222
  expect(last_response.body).to eq('{:custom_formatter=&gt;&quot;rain!&quot;}')
@@ -265,13 +224,9 @@ describe Grape::Middleware::Error do
265
224
  end
266
225
 
267
226
  context do
268
- subject do
269
- Rack::Builder.app do
270
- use Spec::Support::EndpointFaker
271
- use Grape::Middleware::Error
272
- run ExceptionSpec::AccessDeniedApp
273
- end
274
- end
227
+ let(:running_app) { access_denied_app }
228
+ let(:options) { {} }
229
+
275
230
  it 'does not trap regular error! codes' do
276
231
  get '/'
277
232
  expect(last_response.status).to eq(401)
@@ -279,13 +234,9 @@ describe Grape::Middleware::Error do
279
234
  end
280
235
 
281
236
  context do
282
- subject do
283
- Rack::Builder.app do
284
- use Spec::Support::EndpointFaker
285
- use Grape::Middleware::Error, rescue_all: false
286
- run ExceptionSpec::CustomErrorApp
287
- end
288
- end
237
+ let(:running_app) { custom_error_app }
238
+ let(:options) { { rescue_all: false } }
239
+
289
240
  it 'responds to custom Grape exceptions appropriately' do
290
241
  get '/'
291
242
  expect(last_response.status).to eq(400)
@@ -294,16 +245,15 @@ describe Grape::Middleware::Error do
294
245
  end
295
246
 
296
247
  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
248
+ let(:running_app) { exception_app }
249
+ let(:options) do
250
+ {
251
+ rescue_all: true,
252
+ format: :json,
253
+ rescue_options: { backtrace: true, original_exception: true }
254
+ }
306
255
  end
256
+
307
257
  it 'is possible to return the backtrace and the original exception in json format' do
308
258
  get '/'
309
259
  expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original_exception', 'RuntimeError')
@@ -311,16 +261,15 @@ describe Grape::Middleware::Error do
311
261
  end
312
262
 
313
263
  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
264
+ let(:running_app) { exception_app }
265
+ let(:options) do
266
+ {
267
+ rescue_all: true,
268
+ format: :xml,
269
+ rescue_options: { backtrace: true, original_exception: true }
270
+ }
323
271
  end
272
+
324
273
  it 'is possible to return the backtrace and the original exception in xml format' do
325
274
  get '/'
326
275
  expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original-exception', 'RuntimeError')
@@ -328,16 +277,15 @@ describe Grape::Middleware::Error do
328
277
  end
329
278
 
330
279
  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
280
+ let(:running_app) { exception_app }
281
+ let(:options) do
282
+ {
283
+ rescue_all: true,
284
+ format: :txt,
285
+ rescue_options: { backtrace: true, original_exception: true }
286
+ }
340
287
  end
288
+
341
289
  it 'is possible to return the backtrace and the original exception in txt format' do
342
290
  get '/'
343
291
  expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original exception', 'RuntimeError')
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe Grape::Middleware::Formatter do
6
- subject { Grape::Middleware::Formatter.new(app) }
4
+ subject { described_class.new(app) }
5
+
7
6
  before { allow(subject).to receive(:dup).and_return(subject) }
8
7
 
9
8
  let(:body) { { 'foo' => 'bar' } }
@@ -11,6 +10,7 @@ describe Grape::Middleware::Formatter do
11
10
 
12
11
  context 'serialization' do
13
12
  let(:body) { { 'abc' => 'def' } }
13
+
14
14
  it 'looks at the bodies for possibly serializable data' do
15
15
  _, _, bodies = *subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json')
16
16
  bodies.each { |b| expect(b).to eq(::Grape::Json.dump(body)) }
@@ -18,9 +18,10 @@ describe Grape::Middleware::Formatter do
18
18
 
19
19
  context 'default format' do
20
20
  let(:body) { ['foo'] }
21
+
21
22
  it 'calls #to_json since default format is json' do
22
23
  body.instance_eval do
23
- def to_json
24
+ def to_json(*_args)
24
25
  '"bar"'
25
26
  end
26
27
  end
@@ -31,9 +32,10 @@ describe Grape::Middleware::Formatter do
31
32
 
32
33
  context 'jsonapi' do
33
34
  let(:body) { { 'foos' => [{ 'bar' => 'baz' }] } }
35
+
34
36
  it 'calls #to_json if the content type is jsonapi' do
35
37
  body.instance_eval do
36
- def to_json
38
+ def to_json(*_args)
37
39
  '{"foos":[{"bar":"baz"}] }'
38
40
  end
39
41
  end
@@ -44,6 +46,7 @@ describe Grape::Middleware::Formatter do
44
46
 
45
47
  context 'xml' do
46
48
  let(:body) { +'string' }
49
+
47
50
  it 'calls #to_xml if the content type is xml' do
48
51
  body.instance_eval do
49
52
  def to_xml
@@ -58,6 +61,7 @@ describe Grape::Middleware::Formatter do
58
61
 
59
62
  context 'error handling' do
60
63
  let(:formatter) { double(:formatter) }
64
+
61
65
  before do
62
66
  allow(Grape::Formatter).to receive(:formatter_for) { formatter }
63
67
  end
@@ -67,7 +71,7 @@ describe Grape::Middleware::Formatter do
67
71
 
68
72
  expect do
69
73
  catch(:error) { subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }
70
- end.to_not raise_error
74
+ end.not_to raise_error
71
75
  end
72
76
 
73
77
  it 'does not rescue other exceptions' do
@@ -147,7 +151,7 @@ describe Grape::Middleware::Formatter do
147
151
  subject.options[:content_types][:custom] = 'application/vnd.test+json'
148
152
  end
149
153
 
150
- it 'it uses the custom type' do
154
+ it 'uses the custom type' do
151
155
  subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
152
156
  expect(subject.env['api.format']).to eq(:custom)
153
157
  end
@@ -164,26 +168,31 @@ describe Grape::Middleware::Formatter do
164
168
  _, headers, = subject.call('PATH_INFO' => '/info.json')
165
169
  expect(headers['Content-type']).to eq('application/json')
166
170
  end
171
+
167
172
  it 'is set for xml' do
168
173
  _, headers, = subject.call('PATH_INFO' => '/info.xml')
169
174
  expect(headers['Content-type']).to eq('application/xml')
170
175
  end
176
+
171
177
  it 'is set for txt' do
172
178
  _, headers, = subject.call('PATH_INFO' => '/info.txt')
173
179
  expect(headers['Content-type']).to eq('text/plain')
174
180
  end
181
+
175
182
  it 'is set for custom' do
176
183
  subject.options[:content_types] = {}
177
184
  subject.options[:content_types][:custom] = 'application/x-custom'
178
185
  _, headers, = subject.call('PATH_INFO' => '/info.custom')
179
186
  expect(headers['Content-type']).to eq('application/x-custom')
180
187
  end
188
+
181
189
  it 'is set for vendored with registered type' do
182
190
  subject.options[:content_types] = {}
183
191
  subject.options[:content_types][:custom] = 'application/vnd.test+json'
184
192
  _, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
185
193
  expect(headers['Content-type']).to eq('application/vnd.test+json')
186
194
  end
195
+
187
196
  it 'is set to closest generic for custom vendored/versioned without registered type' do
188
197
  _, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
189
198
  expect(headers['Content-type']).to eq('application/json')
@@ -198,13 +207,16 @@ describe Grape::Middleware::Formatter do
198
207
  _, _, body = subject.call('PATH_INFO' => '/info.custom')
199
208
  expect(read_chunks(body)).to eq(['CUSTOM FORMAT'])
200
209
  end
210
+
201
211
  context 'default' do
202
212
  let(:body) { ['blah'] }
213
+
203
214
  it 'uses default json formatter' do
204
215
  _, _, body = subject.call('PATH_INFO' => '/info.json')
205
216
  expect(read_chunks(body)).to eq(['["blah"]'])
206
217
  end
207
218
  end
219
+
208
220
  it 'uses custom json formatter' do
209
221
  subject.options[:formatters][:json] = ->(_obj, _env) { 'CUSTOM JSON FORMAT' }
210
222
  _, _, body = subject.call('PATH_INFO' => '/info.json')
@@ -272,6 +284,7 @@ describe Grape::Middleware::Formatter do
272
284
 
273
285
  context 'when body is nil' do
274
286
  let(:io) { double }
287
+
275
288
  before do
276
289
  allow(io).to receive_message_chain(:rewind, :read).and_return(nil)
277
290
  end
@@ -290,6 +303,7 @@ describe Grape::Middleware::Formatter do
290
303
 
291
304
  context 'when body is empty' do
292
305
  let(:io) { double }
306
+
293
307
  before do
294
308
  allow(io).to receive_message_chain(:rewind, :read).and_return('')
295
309
  end
@@ -334,6 +348,7 @@ describe Grape::Middleware::Formatter do
334
348
  expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
335
349
  expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
336
350
  end
351
+
337
352
  it 'rewinds IO' do
338
353
  io = StringIO.new('{"is_boolean":true,"string":"thing"}')
339
354
  io.read
@@ -347,6 +362,7 @@ describe Grape::Middleware::Formatter do
347
362
  expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
348
363
  expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
349
364
  end
365
+
350
366
  it "parses the body from an xml #{method} and copies values into rack.request.from_hash" do
351
367
  io = StringIO.new('<thing><name>Test</name></thing>')
352
368
  subject.call(
@@ -362,6 +378,7 @@ describe Grape::Middleware::Formatter do
362
378
  expect(subject.env['rack.request.form_hash']['thing']['name']['__content__']).to eq('Test')
363
379
  end
364
380
  end
381
+
365
382
  [Rack::Request::FORM_DATA_MEDIA_TYPES, Rack::Request::PARSEABLE_DATA_MEDIA_TYPES].flatten.each do |content_type|
366
383
  it "ignores #{content_type}" do
367
384
  io = StringIO.new('name=Other+Test+Thing')
@@ -400,10 +417,12 @@ describe Grape::Middleware::Formatter do
400
417
  end
401
418
  end
402
419
  let(:app) { ->(_env) { [200, {}, ['']] } }
420
+
403
421
  before do
404
422
  Grape::Formatter.register :invalid, InvalidFormatter
405
423
  Grape::ContentTypes.register :invalid, 'application/x-invalid'
406
424
  end
425
+
407
426
  after do
408
427
  Grape::ContentTypes.default_elements.delete(:invalid)
409
428
  Grape::Formatter.default_elements.delete(:invalid)
@@ -418,7 +437,7 @@ describe Grape::Middleware::Formatter do
418
437
 
419
438
  context 'custom parser raises exception and rescue options are enabled for backtrace and original_exception' do
420
439
  it 'adds the backtrace and original_exception to the error output' do
421
- subject = Grape::Middleware::Formatter.new(
440
+ subject = described_class.new(
422
441
  app,
423
442
  rescue_options: { backtrace: true, original_exception: true },
424
443
  parsers: { json: ->(_object, _env) { raise StandardError, 'fail' } }
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe Grape::Middleware::Globals do
6
- subject { Grape::Middleware::Globals.new(blank_app) }
4
+ subject { described_class.new(blank_app) }
5
+
7
6
  before { allow(subject).to receive(:dup).and_return(subject) }
8
7
 
9
8
  let(:blank_app) { ->(_env) { [200, {}, 'Hi there.'] } }
@@ -13,15 +12,17 @@ describe Grape::Middleware::Globals do
13
12
  end
14
13
 
15
14
  context 'environment' do
16
- it 'should set the grape.request environment' do
15
+ it 'sets the grape.request environment' do
17
16
  subject.call({})
18
17
  expect(subject.env['grape.request']).to be_a(Grape::Request)
19
18
  end
20
- it 'should set the grape.request.headers environment' do
19
+
20
+ it 'sets the grape.request.headers environment' do
21
21
  subject.call({})
22
22
  expect(subject.env['grape.request.headers']).to be_a(Hash)
23
23
  end
24
- it 'should set the grape.request.params environment' do
24
+
25
+ it 'sets the grape.request.params environment' do
25
26
  subject.call('QUERY_STRING' => 'test=1', 'rack.input' => StringIO.new)
26
27
  expect(subject.env['grape.request.params']).to be_a(Hash)
27
28
  end