grape 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Appraisals +3 -7
- data/CHANGELOG.md +21 -1
- data/Gemfile.lock +26 -26
- data/README.md +109 -29
- data/UPGRADING.md +45 -0
- data/gemfiles/rack_1.5.2.gemfile.lock +232 -0
- data/gemfiles/rails_3.gemfile +1 -1
- data/gemfiles/rails_3.gemfile.lock +288 -0
- data/gemfiles/rails_4.gemfile +1 -1
- data/gemfiles/rails_4.gemfile.lock +280 -0
- data/gemfiles/rails_5.gemfile +1 -1
- data/gemfiles/rails_5.gemfile.lock +312 -0
- data/lib/grape.rb +1 -0
- data/lib/grape/api.rb +74 -195
- data/lib/grape/api/instance.rb +242 -0
- data/lib/grape/dsl/desc.rb +17 -1
- data/lib/grape/dsl/middleware.rb +7 -0
- data/lib/grape/dsl/parameters.rb +9 -4
- data/lib/grape/dsl/routing.rb +5 -1
- data/lib/grape/endpoint.rb +1 -1
- data/lib/grape/exceptions/base.rb +9 -1
- data/lib/grape/exceptions/invalid_response.rb +9 -0
- data/lib/grape/locale/en.yml +1 -0
- data/lib/grape/middleware/error.rb +8 -2
- data/lib/grape/middleware/versioner/header.rb +2 -2
- data/lib/grape/validations/params_scope.rb +1 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/pkg/grape-1.2.0.gem +0 -0
- data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
- data/spec/grape/api_remount_spec.rb +85 -0
- data/spec/grape/api_spec.rb +70 -1
- data/spec/grape/dsl/desc_spec.rb +17 -1
- data/spec/grape/dsl/middleware_spec.rb +8 -0
- data/spec/grape/dsl/routing_spec.rb +10 -0
- data/spec/grape/exceptions/base_spec.rb +61 -0
- data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
- data/spec/grape/middleware/auth/dsl_spec.rb +3 -3
- data/spec/grape/middleware/exception_spec.rb +1 -1
- data/spec/grape/middleware/versioner/header_spec.rb +6 -0
- data/spec/grape/validations/params_scope_spec.rb +133 -0
- data/spec/spec_helper.rb +3 -1
- metadata +99 -87
- data/gemfiles/rack_1.5.2.gemfile +0 -35
- data/pkg/grape-0.17.0.gem +0 -0
- data/pkg/grape-0.19.0.gem +0 -0
data/lib/grape/dsl/desc.rb
CHANGED
@@ -4,6 +4,7 @@ module Grape
|
|
4
4
|
include Grape::DSL::Settings
|
5
5
|
|
6
6
|
# Add a description to the next namespace or function.
|
7
|
+
# @option options :summary [String] summary for this endpoint
|
7
8
|
# @param description [String] descriptive string for this endpoint
|
8
9
|
# or namespace
|
9
10
|
# @param options [Hash] other properties you can set to describe the
|
@@ -17,6 +18,13 @@ module Grape
|
|
17
18
|
# endpoint may return, with their meanings, in a 2d array
|
18
19
|
# @option options :named [String] a specific name to help find this route
|
19
20
|
# @option options :headers [Hash] HTTP headers this method can accept
|
21
|
+
# @option options :hidden [Boolean] hide the endpoint or not
|
22
|
+
# @option options :deprecated [Boolean] deprecate the endpoint or not
|
23
|
+
# @option options :is_array [Boolean] response entity is array or not
|
24
|
+
# @option options :nickname [String] nickname of the endpoint
|
25
|
+
# @option options :produces [Array[String]] a list of MIME types the endpoint produce
|
26
|
+
# @option options :consumes [Array[String]] a list of MIME types the endpoint consume
|
27
|
+
# @option options :tags [Array[String]] a list of tags
|
20
28
|
# @yield a block yielding an instance context with methods mapping to
|
21
29
|
# each of the above, except that :entity is also aliased as #success
|
22
30
|
# and :http_codes is aliased as #failure.
|
@@ -78,13 +86,21 @@ module Grape
|
|
78
86
|
def desc_container
|
79
87
|
Module.new do
|
80
88
|
include Grape::Util::StrictHashConfiguration.module(
|
89
|
+
:summary,
|
81
90
|
:description,
|
82
91
|
:detail,
|
83
92
|
:params,
|
84
93
|
:entity,
|
85
94
|
:http_codes,
|
86
95
|
:named,
|
87
|
-
:headers
|
96
|
+
:headers,
|
97
|
+
:hidden,
|
98
|
+
:deprecated,
|
99
|
+
:is_array,
|
100
|
+
:nickname,
|
101
|
+
:produces,
|
102
|
+
:consumes,
|
103
|
+
:tags
|
88
104
|
)
|
89
105
|
|
90
106
|
def config_context.success(*args)
|
data/lib/grape/dsl/middleware.rb
CHANGED
@@ -21,6 +21,13 @@ module Grape
|
|
21
21
|
namespace_stackable(:middleware, arr)
|
22
22
|
end
|
23
23
|
|
24
|
+
def insert(*args, &block)
|
25
|
+
arr = [:insert, *args]
|
26
|
+
arr << block if block_given?
|
27
|
+
|
28
|
+
namespace_stackable(:middleware, arr)
|
29
|
+
end
|
30
|
+
|
24
31
|
def insert_before(*args, &block)
|
25
32
|
arr = [:insert_before, *args]
|
26
33
|
arr << block if block_given?
|
data/lib/grape/dsl/parameters.rb
CHANGED
@@ -211,10 +211,15 @@ module Grape
|
|
211
211
|
# block yet.
|
212
212
|
# @return [Boolean] whether the parameter has been defined
|
213
213
|
def declared_param?(param)
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
214
|
+
if lateral?
|
215
|
+
# Elements of @declared_params of lateral scope are pushed in @parent. So check them in @parent.
|
216
|
+
@parent.declared_param?(param)
|
217
|
+
else
|
218
|
+
# @declared_params also includes hashes of options and such, but those
|
219
|
+
# won't be flattened out.
|
220
|
+
@declared_params.flatten.any? do |declared_param|
|
221
|
+
first_hash_key_or_param(declared_param) == param
|
222
|
+
end
|
218
223
|
end
|
219
224
|
end
|
220
225
|
|
data/lib/grape/dsl/routing.rb
CHANGED
@@ -77,9 +77,13 @@ module Grape
|
|
77
77
|
namespace_inheritable(:do_not_route_options, true)
|
78
78
|
end
|
79
79
|
|
80
|
-
def mount(mounts)
|
80
|
+
def mount(mounts, opts = {})
|
81
81
|
mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
|
82
82
|
mounts.each_pair do |app, path|
|
83
|
+
if app.respond_to?(:mount_instance)
|
84
|
+
mount(app.mount_instance(configuration: opts[:with] || {}) => path)
|
85
|
+
next
|
86
|
+
end
|
83
87
|
in_setting = inheritable_setting
|
84
88
|
|
85
89
|
if app.respond_to?(:inheritable_setting, true)
|
data/lib/grape/endpoint.rb
CHANGED
@@ -74,7 +74,15 @@ module Grape
|
|
74
74
|
options = options.dup
|
75
75
|
options[:default] &&= options[:default].to_s
|
76
76
|
message = ::I18n.translate(key, **options)
|
77
|
-
message.present? ? message :
|
77
|
+
message.present? ? message : fallback_message(key, **options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def fallback_message(key, **options)
|
81
|
+
if ::I18n.enforce_available_locales && !::I18n.available_locales.include?(FALLBACK_LOCALE)
|
82
|
+
key
|
83
|
+
else
|
84
|
+
::I18n.translate(key, locale: FALLBACK_LOCALE, **options)
|
85
|
+
end
|
78
86
|
end
|
79
87
|
end
|
80
88
|
end
|
data/lib/grape/locale/en.yml
CHANGED
@@ -73,7 +73,7 @@ module Grape
|
|
73
73
|
if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
|
74
74
|
message = ERB::Util.html_escape(message)
|
75
75
|
end
|
76
|
-
Rack::Response.new([message], status, headers)
|
76
|
+
Rack::Response.new([message], status, headers)
|
77
77
|
end
|
78
78
|
|
79
79
|
def format_message(message, backtrace, original_exception = nil)
|
@@ -127,7 +127,13 @@ module Grape
|
|
127
127
|
handler = public_method(handler)
|
128
128
|
end
|
129
129
|
|
130
|
-
handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
|
130
|
+
response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
|
131
|
+
|
132
|
+
if response.is_a?(Rack::Response)
|
133
|
+
response
|
134
|
+
else
|
135
|
+
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
|
136
|
+
end
|
131
137
|
end
|
132
138
|
end
|
133
139
|
end
|
@@ -173,7 +173,7 @@ module Grape
|
|
173
173
|
# @return [Boolean] whether the content type sets a vendor
|
174
174
|
def vendor?(media_type)
|
175
175
|
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
176
|
-
subtype[HAS_VENDOR_REGEX]
|
176
|
+
subtype.present? && subtype[HAS_VENDOR_REGEX]
|
177
177
|
end
|
178
178
|
|
179
179
|
def request_vendor(media_type)
|
@@ -190,7 +190,7 @@ module Grape
|
|
190
190
|
# @return [Boolean] whether the content type sets an API version
|
191
191
|
def version?(media_type)
|
192
192
|
_, subtype = Rack::Accept::Header.parse_media_type(media_type)
|
193
|
-
subtype[HAS_VERSION_REGEX]
|
193
|
+
subtype.present? && subtype[HAS_VERSION_REGEX]
|
194
194
|
end
|
195
195
|
end
|
196
196
|
end
|
@@ -121,6 +121,7 @@ module Grape
|
|
121
121
|
if opts && opts[:as]
|
122
122
|
@api.route_setting(:aliased_params, @api.route_setting(:aliased_params) || [])
|
123
123
|
@api.route_setting(:aliased_params) << { attrs.first => opts[:as] }
|
124
|
+
attrs = [opts[:as]]
|
124
125
|
end
|
125
126
|
|
126
127
|
@declared_params.concat attrs
|
data/lib/grape/version.rb
CHANGED
data/pkg/grape-1.2.0.gem
ADDED
Binary file
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Endpoint do
|
4
|
+
subject { Class.new(Grape::API) }
|
5
|
+
|
6
|
+
def app
|
7
|
+
subject
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'get' do
|
11
|
+
it 'routes to a namespace param with dots' do
|
12
|
+
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
|
13
|
+
get '/' do
|
14
|
+
params[:ns_with_dots]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/test.id.with.dots'
|
19
|
+
expect(last_response.status).to eq 200
|
20
|
+
expect(last_response.body).to eq 'test.id.with.dots'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'routes to a path with multiple params with dots' do
|
24
|
+
subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^\/]+},
|
25
|
+
another_id_with_dots: %r{[^\/]+} } do
|
26
|
+
"#{params[:id_with_dots]}/#{params[:another_id_with_dots]}"
|
27
|
+
end
|
28
|
+
|
29
|
+
get '/test.id/test2.id'
|
30
|
+
expect(last_response.status).to eq 200
|
31
|
+
expect(last_response.body).to eq 'test.id/test2.id'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'routes to namespace and path params with dots, with overridden requirements' do
|
35
|
+
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
|
36
|
+
get ':another_id_with_dots', requirements: { ns_with_dots: %r{[^\/]+},
|
37
|
+
another_id_with_dots: %r{[^\/]+} } do
|
38
|
+
"#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
get '/test.id/test2.id'
|
43
|
+
expect(last_response.status).to eq 200
|
44
|
+
expect(last_response.body).to eq 'test.id/test2.id'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'routes to namespace and path params with dots, with merged requirements' do
|
48
|
+
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
|
49
|
+
get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[^\/]+} } do
|
50
|
+
"#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
get '/test.id/test2.id'
|
55
|
+
expect(last_response.status).to eq 200
|
56
|
+
expect(last_response.body).to eq 'test.id/test2.id'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'shared/versioning_examples'
|
3
|
+
|
4
|
+
describe Grape::API do
|
5
|
+
subject(:a_remounted_api) { Class.new(Grape::API) }
|
6
|
+
let(:root_api) { Class.new(Grape::API) }
|
7
|
+
|
8
|
+
def app
|
9
|
+
root_api
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'remounting an API' do
|
13
|
+
context 'with a defined route' do
|
14
|
+
before do
|
15
|
+
a_remounted_api.get '/votes' do
|
16
|
+
'10 votes'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when mounting one instance' do
|
21
|
+
before do
|
22
|
+
root_api.mount a_remounted_api
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'can access the endpoint' do
|
26
|
+
get '/votes'
|
27
|
+
expect(last_response.body).to eql '10 votes'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when mounting twice' do
|
32
|
+
before do
|
33
|
+
root_api.mount a_remounted_api => '/posts'
|
34
|
+
root_api.mount a_remounted_api => '/comments'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'can access the votes in both places' do
|
38
|
+
get '/posts/votes'
|
39
|
+
expect(last_response.body).to eql '10 votes'
|
40
|
+
get '/comments/votes'
|
41
|
+
expect(last_response.body).to eql '10 votes'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when mounting on namespace' do
|
46
|
+
before do
|
47
|
+
stub_const('StaticRefToAPI', a_remounted_api)
|
48
|
+
root_api.namespace 'posts' do
|
49
|
+
mount StaticRefToAPI
|
50
|
+
end
|
51
|
+
|
52
|
+
root_api.namespace 'comments' do
|
53
|
+
mount StaticRefToAPI
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'can access the votes in both places' do
|
58
|
+
get '/posts/votes'
|
59
|
+
expect(last_response.body).to eql '10 votes'
|
60
|
+
get '/comments/votes'
|
61
|
+
expect(last_response.body).to eql '10 votes'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with a dynamically configured route' do
|
67
|
+
before do
|
68
|
+
a_remounted_api.namespace 'api' do
|
69
|
+
get "/#{configuration[:path]}" do
|
70
|
+
'10 votes'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
root_api.mount a_remounted_api, with: { path: 'votes' }
|
74
|
+
root_api.mount a_remounted_api, with: { path: 'scores' }
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'will use the dynamic configuration on all routes' do
|
78
|
+
get 'api/votes'
|
79
|
+
expect(last_response.body).to eql '10 votes'
|
80
|
+
get 'api/scores'
|
81
|
+
expect(last_response.body).to eql '10 votes'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/spec/grape/api_spec.rb
CHANGED
@@ -1348,6 +1348,28 @@ XML
|
|
1348
1348
|
end
|
1349
1349
|
end
|
1350
1350
|
|
1351
|
+
describe '.insert' do
|
1352
|
+
it 'inserts middleware in a specific location in the stack' do
|
1353
|
+
m = Class.new(Grape::Middleware::Base) do
|
1354
|
+
def call(env)
|
1355
|
+
env['phony.args'] ||= []
|
1356
|
+
env['phony.args'] << @options[:message]
|
1357
|
+
@app.call(env)
|
1358
|
+
end
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
subject.use ApiSpec::PhonyMiddleware, 'bye'
|
1362
|
+
subject.insert 0, m, message: 'good'
|
1363
|
+
subject.insert 0, m, message: 'hello'
|
1364
|
+
subject.get '/' do
|
1365
|
+
env['phony.args'].join(' ')
|
1366
|
+
end
|
1367
|
+
|
1368
|
+
get '/'
|
1369
|
+
expect(last_response.body).to eql 'hello good bye'
|
1370
|
+
end
|
1371
|
+
end
|
1372
|
+
|
1351
1373
|
describe '.http_basic' do
|
1352
1374
|
it 'protects any resources on the same scope' do
|
1353
1375
|
subject.http_basic do |u, _p|
|
@@ -1723,6 +1745,16 @@ XML
|
|
1723
1745
|
expect(last_response.status).to eql 500
|
1724
1746
|
expect(last_response.body).to eq('Formatter Error')
|
1725
1747
|
end
|
1748
|
+
|
1749
|
+
it 'uses default_rescue_handler to handle invalid response from rescue_from' do
|
1750
|
+
subject.rescue_from(:all) { 'error' }
|
1751
|
+
subject.get('/') { raise }
|
1752
|
+
|
1753
|
+
expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
|
1754
|
+
get '/'
|
1755
|
+
expect(last_response.status).to eql 500
|
1756
|
+
expect(last_response.body).to eql 'Invalid response'
|
1757
|
+
end
|
1726
1758
|
end
|
1727
1759
|
|
1728
1760
|
describe '.rescue_from klass, block' do
|
@@ -3177,6 +3209,43 @@ XML
|
|
3177
3209
|
expect { a.mount b }.to_not raise_error
|
3178
3210
|
end
|
3179
3211
|
end
|
3212
|
+
|
3213
|
+
context 'when including a module' do
|
3214
|
+
let(:included_module) do
|
3215
|
+
Module.new do
|
3216
|
+
def self.included(base)
|
3217
|
+
base.extend(ClassMethods)
|
3218
|
+
end
|
3219
|
+
module ClassMethods
|
3220
|
+
def my_method
|
3221
|
+
@test = true
|
3222
|
+
end
|
3223
|
+
end
|
3224
|
+
end
|
3225
|
+
end
|
3226
|
+
|
3227
|
+
it 'should correctly include module in nested mount' do
|
3228
|
+
module_to_include = included_module
|
3229
|
+
v1 = Class.new(Grape::API) do
|
3230
|
+
version :v1, using: :path
|
3231
|
+
include module_to_include
|
3232
|
+
my_method
|
3233
|
+
end
|
3234
|
+
v2 = Class.new(Grape::API) do
|
3235
|
+
version :v2, using: :path
|
3236
|
+
end
|
3237
|
+
segment_base = Class.new(Grape::API) do
|
3238
|
+
mount v1
|
3239
|
+
mount v2
|
3240
|
+
end
|
3241
|
+
|
3242
|
+
Class.new(Grape::API) do
|
3243
|
+
mount segment_base
|
3244
|
+
end
|
3245
|
+
|
3246
|
+
expect(v1.my_method).to be_truthy
|
3247
|
+
end
|
3248
|
+
end
|
3180
3249
|
end
|
3181
3250
|
end
|
3182
3251
|
|
@@ -3192,7 +3261,7 @@ XML
|
|
3192
3261
|
it 'sets the instance' do
|
3193
3262
|
expect(subject.instance).to be_nil
|
3194
3263
|
subject.compile
|
3195
|
-
expect(subject.instance).to be_kind_of(subject)
|
3264
|
+
expect(subject.instance).to be_kind_of(subject.base_instance)
|
3196
3265
|
end
|
3197
3266
|
end
|
3198
3267
|
|