grape 1.5.2 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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