grape 1.1.0 → 1.2.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.
- 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
|
|