grape 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

@@ -45,7 +45,7 @@ module Grape
45
45
  Rack::Response.new(bodymap, status, headers)
46
46
  end
47
47
  rescue Grape::Exceptions::InvalidFormatter => e
48
- throw :error, status: 500, message: e.message
48
+ throw :error, status: 500, message: e.message, backtrace: e.backtrace, original_exception: e
49
49
  end
50
50
 
51
51
  def fetch_formatter(headers, options)
@@ -110,7 +110,7 @@ module Grape
110
110
  rescue Grape::Exceptions::Base => e
111
111
  raise e
112
112
  rescue StandardError => e
113
- throw :error, status: 400, message: e.message
113
+ throw :error, status: 400, message: e.message, backtrace: e.backtrace, original_exception: e
114
114
  end
115
115
  else
116
116
  env[Grape::Env::API_REQUEST_BODY] = body
@@ -46,7 +46,11 @@ module Grape
46
46
  parent.should_validate?(parameters)
47
47
  end
48
48
 
49
- def meets_dependency?(params)
49
+ def meets_dependency?(params, request_params)
50
+ if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
51
+ return false
52
+ end
53
+
50
54
  return true unless @dependent_on
51
55
 
52
56
  params = params.with_indifferent_access
@@ -111,10 +115,15 @@ module Grape
111
115
 
112
116
  # Adds a parameter declaration to our list of validations.
113
117
  # @param attrs [Array] (see Grape::DSL::Parameters#requires)
114
- def push_declared_params(attrs)
118
+ def push_declared_params(attrs, **opts)
115
119
  if lateral?
116
120
  @parent.push_declared_params(attrs)
117
121
  else
122
+ if opts && opts[:as]
123
+ @api.route_setting(:aliased_params, @api.route_setting(:aliased_params) || [])
124
+ @api.route_setting(:aliased_params) << { attrs.first => opts[:as] }
125
+ end
126
+
118
127
  @declared_params.concat attrs
119
128
  end
120
129
  end
@@ -0,0 +1,15 @@
1
+ module Grape
2
+ module Validations
3
+ class AsValidator < Base
4
+ def initialize(attrs, options, required, scope, opts = {})
5
+ @alias = options
6
+ super
7
+ end
8
+
9
+ def validate_param!(attr_name, params)
10
+ params[@alias] = params[attr_name]
11
+ params.delete(attr_name)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -39,7 +39,7 @@ module Grape
39
39
  array_errors = []
40
40
  attributes.each do |resource_params, attr_name|
41
41
  next unless @required || (resource_params.respond_to?(:key?) && resource_params.key?(attr_name))
42
- next unless @scope.meets_dependency?(resource_params)
42
+ next unless @scope.meets_dependency?(resource_params, params)
43
43
 
44
44
  begin
45
45
  validate_param!(attr_name, resource_params)
@@ -32,7 +32,7 @@ module Grape
32
32
  unless check_excepts(param_array)
33
33
 
34
34
  raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values) \
35
- unless check_values(param_array)
35
+ unless check_values(param_array, attr_name)
36
36
 
37
37
  raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values) \
38
38
  if @proc && !param_array.all? { |param| @proc.call(param) }
@@ -40,10 +40,15 @@ module Grape
40
40
 
41
41
  private
42
42
 
43
- def check_values(param_array)
43
+ def check_values(param_array, attr_name)
44
44
  values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values
45
45
  return true if values.nil?
46
- return param_array.all? { |param| values.call(param) } if values.is_a? Proc
46
+ begin
47
+ return param_array.all? { |param| values.call(param) } if values.is_a? Proc
48
+ rescue StandardError => e
49
+ warn "Error '#{e}' raised while validating attribute '#{attr_name}'"
50
+ return false
51
+ end
47
52
  param_array.all? { |param| values.include?(param) }
48
53
  end
49
54
 
data/lib/grape/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Grape
2
2
  # The current version of Grape.
3
- VERSION = '1.0.0'.freeze
3
+ VERSION = '1.0.1'.freeze
4
4
  end
Binary file
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::API::Helpers do
4
+ let(:user) { 'Miguel Caneo' }
5
+ let(:id) { '42' }
6
+
7
+ module InheritedHelpersSpec
8
+ class SuperClass < Grape::API
9
+ helpers do
10
+ params(:superclass_params) { requires :id, type: String }
11
+
12
+ def current_user
13
+ params[:user]
14
+ end
15
+ end
16
+ end
17
+
18
+ class OverriddenSubClass < SuperClass
19
+ params { use :superclass_params }
20
+
21
+ helpers do
22
+ def current_user
23
+ "#{params[:user]} with id"
24
+ end
25
+ end
26
+
27
+ get 'resource' do
28
+ "#{current_user}: #{params['id']}"
29
+ end
30
+ end
31
+
32
+ class SubClass < SuperClass
33
+ params { use :superclass_params }
34
+
35
+ get 'resource' do
36
+ "#{current_user}: #{params['id']}"
37
+ end
38
+ end
39
+
40
+ class Example < SubClass
41
+ params { use :superclass_params }
42
+
43
+ get 'resource' do
44
+ "#{current_user}: #{params['id']}"
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'non overriding subclass' do
50
+ subject { InheritedHelpersSpec::SubClass }
51
+
52
+ def app
53
+ subject
54
+ end
55
+
56
+ context 'given expected params' do
57
+ it 'inherits helpers from a superclass' do
58
+ get '/resource', id: id, user: user
59
+ expect(last_response.body).to eq("#{user}: #{id}")
60
+ end
61
+ end
62
+
63
+ context 'with lack of expected params' do
64
+ it 'returns missing error' do
65
+ get '/resource'
66
+ expect(last_response.body).to eq('id is missing')
67
+ end
68
+ end
69
+ end
70
+
71
+ context 'overriding subclass' do
72
+ subject { InheritedHelpersSpec::OverriddenSubClass }
73
+
74
+ def app
75
+ subject
76
+ end
77
+
78
+ context 'given expected params' do
79
+ it 'overrides helpers from a superclass' do
80
+ get '/resource', id: id, user: user
81
+ expect(last_response.body).to eq("#{user} with id: #{id}")
82
+ end
83
+ end
84
+
85
+ context 'with lack of expected params' do
86
+ it 'returns missing error' do
87
+ get '/resource'
88
+ expect(last_response.body).to eq('id is missing')
89
+ end
90
+ end
91
+ end
92
+
93
+ context 'example subclass' do
94
+ subject { InheritedHelpersSpec::Example }
95
+
96
+ def app
97
+ subject
98
+ end
99
+
100
+ context 'given expected params' do
101
+ it 'inherits helpers from a superclass' do
102
+ get '/resource', id: id, user: user
103
+ expect(last_response.body).to eq("#{user}: #{id}")
104
+ end
105
+ end
106
+
107
+ context 'with lack of expected params' do
108
+ it 'returns missing error' do
109
+ get '/resource'
110
+ expect(last_response.body).to eq('id is missing')
111
+ end
112
+ end
113
+ end
114
+ end
@@ -1996,7 +1996,7 @@ XML
1996
1996
  before :each do
1997
1997
  module ApiSpec
1998
1998
  class CustomErrorFormatter
1999
- def self.call(message, _backtrace, _options, _env)
1999
+ def self.call(message, _backtrace, _options, _env, _original_exception)
2000
2000
  "message: #{message} @backtrace"
2001
2001
  end
2002
2002
  end
@@ -2018,7 +2018,7 @@ XML
2018
2018
  before :each do
2019
2019
  module ApiSpec
2020
2020
  class CustomErrorFormatter
2021
- def self.call(message, _backtrace, _option, _env)
2021
+ def self.call(message, _backtrace, _option, _env, _original_exception)
2022
2022
  "message: #{message} @backtrace"
2023
2023
  end
2024
2024
  end
@@ -24,6 +24,12 @@ module Grape
24
24
  end
25
25
  end
26
26
 
27
+ class Base < Grape::API
28
+ helpers BooleanParam
29
+ end
30
+
31
+ class Child < Base; end
32
+
27
33
  describe Helpers do
28
34
  subject { Class.new(HelpersSpec::Dummy) }
29
35
  let(:proc) do
@@ -74,6 +80,19 @@ module Grape
74
80
  expect(subject.first_mod::Boolean).to eq Virtus::Attribute::Boolean
75
81
  end
76
82
  end
83
+
84
+ context 'in child classes' do
85
+ it 'is available' do
86
+ klass = Child
87
+ expect do
88
+ klass.instance_eval do
89
+ params do
90
+ use :requires_toggle_prm
91
+ end
92
+ end
93
+ end.to_not raise_exception
94
+ end
95
+ end
77
96
  end
78
97
  end
79
98
  end
@@ -15,7 +15,7 @@ module Grape
15
15
  @validate_attributes
16
16
  end
17
17
 
18
- def push_declared_params(*args)
18
+ def push_declared_params(args, **_opts)
19
19
  @push_declared_params = args
20
20
  end
21
21
 
@@ -84,7 +84,7 @@ module Grape
84
84
  subject.requires :id, type: Integer, desc: 'Identity.'
85
85
 
86
86
  expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.', presence: { value: true, message: nil } }])
87
- expect(subject.push_declared_params_reader).to eq([[:id]])
87
+ expect(subject.push_declared_params_reader).to eq([:id])
88
88
  end
89
89
  end
90
90
 
@@ -93,7 +93,7 @@ module Grape
93
93
  subject.optional :id, type: Integer, desc: 'Identity.'
94
94
 
95
95
  expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.' }])
96
- expect(subject.push_declared_params_reader).to eq([[:id]])
96
+ expect(subject.push_declared_params_reader).to eq([:id])
97
97
  end
98
98
  end
99
99
 
@@ -102,7 +102,7 @@ module Grape
102
102
  subject.with(type: Integer) { subject.optional :id, desc: 'Identity.' }
103
103
 
104
104
  expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.' }])
105
- expect(subject.push_declared_params_reader).to eq([[:id]])
105
+ expect(subject.push_declared_params_reader).to eq([:id])
106
106
  end
107
107
  end
108
108
 
@@ -50,147 +50,241 @@ describe Grape::Middleware::Error do
50
50
  end
51
51
  end
52
52
 
53
- attr_reader :app
53
+ def app
54
+ subject
55
+ end
54
56
 
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
57
+ context 'with defaults' do
58
+ subject do
59
+ Rack::Builder.app do
60
+ use Spec::Support::EndpointFaker
61
+ use Grape::Middleware::Error
62
+ run ExceptionSpec::ExceptionApp
63
+ end
64
+ end
65
+ it 'does not trap errors by default' do
66
+ expect { get '/' }.to raise_error(RuntimeError, 'rain!')
60
67
  end
61
- expect { get '/' }.to raise_error(RuntimeError, 'rain!')
62
68
  end
63
69
 
64
- context 'with rescue_all set to true' do
65
- it 'sets the message appropriately' do
66
- @app ||= Rack::Builder.app do
70
+ context 'with rescue_all' do
71
+ subject do
72
+ Rack::Builder.app do
67
73
  use Spec::Support::EndpointFaker
68
74
  use Grape::Middleware::Error, rescue_all: true
69
75
  run ExceptionSpec::ExceptionApp
70
76
  end
77
+ end
78
+ it 'sets the message appropriately' do
71
79
  get '/'
72
80
  expect(last_response.body).to eq('rain!')
73
81
  end
74
-
75
82
  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
80
- end
81
83
  get '/'
82
84
  expect(last_response.status).to eq(500)
83
85
  end
86
+ end
84
87
 
85
- it 'is possible to specify a different default status code' do
86
- @app ||= Rack::Builder.app do
88
+ context do
89
+ subject do
90
+ Rack::Builder.app do
87
91
  use Spec::Support::EndpointFaker
88
92
  use Grape::Middleware::Error, rescue_all: true, default_status: 500
89
93
  run ExceptionSpec::ExceptionApp
90
94
  end
95
+ end
96
+ it 'is possible to specify a different default status code' do
91
97
  get '/'
92
98
  expect(last_response.status).to eq(500)
93
99
  end
100
+ end
94
101
 
95
- it 'is possible to return errors in json format' do
96
- @app ||= Rack::Builder.app do
102
+ context do
103
+ subject do
104
+ Rack::Builder.app do
97
105
  use Spec::Support::EndpointFaker
98
106
  use Grape::Middleware::Error, rescue_all: true, format: :json
99
107
  run ExceptionSpec::ExceptionApp
100
108
  end
109
+ end
110
+ it 'is possible to return errors in json format' do
101
111
  get '/'
102
112
  expect(last_response.body).to eq('{"error":"rain!"}')
103
113
  end
114
+ end
104
115
 
105
- it 'is possible to return hash errors in json format' do
106
- @app ||= Rack::Builder.app do
116
+ context do
117
+ subject do
118
+ Rack::Builder.app do
107
119
  use Spec::Support::EndpointFaker
108
120
  use Grape::Middleware::Error, rescue_all: true, format: :json
109
121
  run ExceptionSpec::ErrorHashApp
110
122
  end
123
+ end
124
+ it 'is possible to return hash errors in json format' do
111
125
  get '/'
112
126
  expect(['{"error":"rain!","detail":"missing widget"}',
113
127
  '{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
114
128
  end
129
+ end
115
130
 
116
- it 'is possible to return errors in jsonapi format' do
117
- @app ||= Rack::Builder.app do
131
+ context do
132
+ subject do
133
+ Rack::Builder.app do
118
134
  use Spec::Support::EndpointFaker
119
135
  use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
120
136
  run ExceptionSpec::ExceptionApp
121
137
  end
138
+ end
139
+ it 'is possible to return errors in jsonapi format' do
122
140
  get '/'
123
141
  expect(last_response.body).to eq('{"error":"rain!"}')
124
142
  end
143
+ end
125
144
 
126
- it 'is possible to return hash errors in jsonapi format' do
127
- @app ||= Rack::Builder.app do
145
+ context do
146
+ subject do
147
+ Rack::Builder.app do
128
148
  use Spec::Support::EndpointFaker
129
149
  use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
130
150
  run ExceptionSpec::ErrorHashApp
131
151
  end
152
+ end
153
+
154
+ it 'is possible to return hash errors in jsonapi format' do
132
155
  get '/'
133
156
  expect(['{"error":"rain!","detail":"missing widget"}',
134
157
  '{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
135
158
  end
159
+ end
136
160
 
137
- it 'is possible to return errors in xml format' do
138
- @app ||= Rack::Builder.app do
161
+ context do
162
+ subject do
163
+ Rack::Builder.app do
139
164
  use Spec::Support::EndpointFaker
140
165
  use Grape::Middleware::Error, rescue_all: true, format: :xml
141
166
  run ExceptionSpec::ExceptionApp
142
167
  end
168
+ end
169
+ it 'is possible to return errors in xml format' do
143
170
  get '/'
144
171
  expect(last_response.body).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <message>rain!</message>\n</error>\n")
145
172
  end
173
+ end
146
174
 
147
- it 'is possible to return hash errors in xml format' do
148
- @app ||= Rack::Builder.app do
175
+ context do
176
+ subject do
177
+ Rack::Builder.app do
149
178
  use Spec::Support::EndpointFaker
150
179
  use Grape::Middleware::Error, rescue_all: true, format: :xml
151
180
  run ExceptionSpec::ErrorHashApp
152
181
  end
182
+ end
183
+ it 'is possible to return hash errors in xml format' do
153
184
  get '/'
154
185
  expect(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <detail>missing widget</detail>\n <error>rain!</error>\n</error>\n",
155
186
  "<?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
187
  end
188
+ end
157
189
 
158
- it 'is possible to specify a custom formatter' do
159
- @app ||= Rack::Builder.app do
190
+ context do
191
+ subject do
192
+ Rack::Builder.app do
160
193
  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
- }
194
+ use Grape::Middleware::Error,
195
+ rescue_all: true,
196
+ format: :custom,
197
+ error_formatters: {
198
+ custom: lambda do |message, _backtrace, _options, _env, _original_exception|
199
+ { custom_formatter: message }.inspect
200
+ end
201
+ }
168
202
  run ExceptionSpec::ExceptionApp
169
203
  end
204
+ end
205
+ it 'is possible to specify a custom formatter' do
170
206
  get '/'
171
207
  expect(last_response.body).to eq('{:custom_formatter=>"rain!"}')
172
208
  end
209
+ end
173
210
 
174
- it 'does not trap regular error! codes' do
175
- @app ||= Rack::Builder.app do
211
+ context do
212
+ subject do
213
+ Rack::Builder.app do
176
214
  use Spec::Support::EndpointFaker
177
215
  use Grape::Middleware::Error
178
216
  run ExceptionSpec::AccessDeniedApp
179
217
  end
218
+ end
219
+ it 'does not trap regular error! codes' do
180
220
  get '/'
181
221
  expect(last_response.status).to eq(401)
182
222
  end
223
+ end
183
224
 
184
- it 'responds to custom Grape exceptions appropriately' do
185
- @app ||= Rack::Builder.app do
225
+ context do
226
+ subject do
227
+ Rack::Builder.app do
186
228
  use Spec::Support::EndpointFaker
187
229
  use Grape::Middleware::Error, rescue_all: false
188
230
  run ExceptionSpec::CustomErrorApp
189
231
  end
190
-
232
+ end
233
+ it 'responds to custom Grape exceptions appropriately' do
191
234
  get '/'
192
235
  expect(last_response.status).to eq(400)
193
236
  expect(last_response.body).to eq('failed validation')
194
237
  end
195
238
  end
239
+
240
+ context 'with rescue_options :backtrace and :exception set to true' do
241
+ subject do
242
+ Rack::Builder.app do
243
+ use Spec::Support::EndpointFaker
244
+ use Grape::Middleware::Error,
245
+ rescue_all: true,
246
+ format: :json,
247
+ rescue_options: { backtrace: true, original_exception: true }
248
+ run ExceptionSpec::ExceptionApp
249
+ end
250
+ end
251
+ it 'is possible to return the backtrace and the original exception in json format' do
252
+ get '/'
253
+ expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original_exception', 'RuntimeError')
254
+ end
255
+ end
256
+
257
+ context do
258
+ subject do
259
+ Rack::Builder.app do
260
+ use Spec::Support::EndpointFaker
261
+ use Grape::Middleware::Error,
262
+ rescue_all: true,
263
+ format: :xml,
264
+ rescue_options: { backtrace: true, original_exception: true }
265
+ run ExceptionSpec::ExceptionApp
266
+ end
267
+ end
268
+ it 'is possible to return the backtrace and the original exception in xml format' do
269
+ get '/'
270
+ expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original-exception', 'RuntimeError')
271
+ end
272
+ end
273
+
274
+ context do
275
+ subject do
276
+ Rack::Builder.app do
277
+ use Spec::Support::EndpointFaker
278
+ use Grape::Middleware::Error,
279
+ rescue_all: true,
280
+ format: :txt,
281
+ rescue_options: { backtrace: true, original_exception: true }
282
+ run ExceptionSpec::ExceptionApp
283
+ end
284
+ end
285
+ it 'is possible to return the backtrace and the original exception in txt format' do
286
+ get '/'
287
+ expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original exception', 'RuntimeError')
288
+ end
289
+ end
196
290
  end