grape 1.5.0 → 1.6.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/CHANGELOG.md +51 -2
- data/README.md +43 -10
- data/UPGRADING.md +91 -0
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +13 -17
- data/lib/grape/api.rb +7 -14
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/helpers.rb +6 -4
- data/lib/grape/dsl/inside_route.rb +18 -9
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +11 -7
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +7 -6
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/endpoint.rb +21 -36
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/validation.rb +2 -3
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -1
- data/lib/grape/formatter/xml.rb +1 -0
- data/lib/grape/locale/en.yml +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/base.rb +4 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +10 -16
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +3 -0
- data/lib/grape/router/attribute_translator.rb +1 -1
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +6 -0
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/lazy_value.rb +3 -2
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +92 -58
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +3 -3
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/all_or_none.rb +1 -0
- data/lib/grape/validations/validators/as.rb +4 -8
- data/lib/grape/validations/validators/at_least_one_of.rb +1 -0
- data/lib/grape/validations/validators/base.rb +12 -7
- data/lib/grape/validations/validators/coerce.rb +8 -9
- data/lib/grape/validations/validators/default.rb +1 -0
- data/lib/grape/validations/validators/exactly_one_of.rb +1 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +5 -2
- data/lib/grape/validations/validators/mutual_exclusion.rb +1 -0
- data/lib/grape/validations/validators/presence.rb +1 -0
- data/lib/grape/validations/validators/regexp.rb +1 -0
- data/lib/grape/validations/validators/same_as.rb +1 -0
- data/lib/grape/validations/validators/values.rb +3 -0
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +3 -1
- data/spec/grape/api/custom_validations_spec.rb +1 -0
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
- data/spec/grape/api_remount_spec.rb +9 -4
- data/spec/grape/api_spec.rb +203 -37
- data/spec/grape/dsl/callbacks_spec.rb +1 -1
- data/spec/grape/dsl/middleware_spec.rb +1 -1
- data/spec/grape/dsl/parameters_spec.rb +1 -0
- data/spec/grape/dsl/routing_spec.rb +1 -1
- data/spec/grape/endpoint/declared_spec.rb +259 -1
- data/spec/grape/endpoint_spec.rb +18 -5
- data/spec/grape/entity_spec.rb +10 -10
- data/spec/grape/middleware/auth/dsl_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -2
- data/spec/grape/middleware/formatter_spec.rb +2 -2
- data/spec/grape/middleware/stack_spec.rb +4 -3
- data/spec/grape/request_spec.rb +1 -1
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
- data/spec/grape/validations/params_scope_spec.rb +37 -3
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
- data/spec/grape/validations/validators/coerce_spec.rb +129 -22
- data/spec/grape/validations/validators/except_values_spec.rb +2 -2
- data/spec/grape/validations/validators/values_spec.rb +15 -11
- data/spec/grape/validations_spec.rb +280 -0
- data/spec/shared/versioning_examples.rb +22 -22
- data/spec/spec_helper.rb +1 -1
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +1 -1
- metadata +8 -6
@@ -13,6 +13,7 @@ module Grape
|
|
13
13
|
'Use the except validator instead.' if @excepts
|
14
14
|
|
15
15
|
raise ArgumentError, 'proc must be a Proc' if @proc && !@proc.is_a?(Proc)
|
16
|
+
|
16
17
|
warn '[DEPRECATION] The values validator proc option is deprecated. ' \
|
17
18
|
'The lambda expression can now be assigned directly to values.' if @proc
|
18
19
|
else
|
@@ -51,6 +52,7 @@ module Grape
|
|
51
52
|
def check_values(param_array, attr_name)
|
52
53
|
values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values
|
53
54
|
return true if values.nil?
|
55
|
+
|
54
56
|
begin
|
55
57
|
return param_array.all? { |param| values.call(param) } if values.is_a? Proc
|
56
58
|
rescue StandardError => e
|
@@ -63,6 +65,7 @@ module Grape
|
|
63
65
|
def check_excepts(param_array)
|
64
66
|
excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts
|
65
67
|
return true if excepts.nil?
|
68
|
+
|
66
69
|
param_array.none? { |param| excepts.include?(param) }
|
67
70
|
end
|
68
71
|
|
data/lib/grape/version.rb
CHANGED
data/lib/grape.rb
CHANGED
@@ -12,6 +12,7 @@ require 'active_support/core_ext/hash/indifferent_access'
|
|
12
12
|
require 'active_support/core_ext/object/blank'
|
13
13
|
require 'active_support/core_ext/array/extract_options'
|
14
14
|
require 'active_support/core_ext/array/wrap'
|
15
|
+
require 'active_support/core_ext/array/conversions'
|
15
16
|
require 'active_support/core_ext/hash/deep_merge'
|
16
17
|
require 'active_support/core_ext/hash/reverse_merge'
|
17
18
|
require 'active_support/core_ext/hash/except'
|
@@ -21,7 +22,7 @@ require 'active_support/dependencies/autoload'
|
|
21
22
|
require 'active_support/notifications'
|
22
23
|
require 'i18n'
|
23
24
|
|
24
|
-
I18n.load_path << File.expand_path('
|
25
|
+
I18n.load_path << File.expand_path('grape/locale/en.yml', __dir__)
|
25
26
|
|
26
27
|
module Grape
|
27
28
|
extend ::ActiveSupport::Autoload
|
@@ -75,6 +76,7 @@ module Grape
|
|
75
76
|
autoload :InvalidVersionHeader
|
76
77
|
autoload :MethodNotAllowed
|
77
78
|
autoload :InvalidResponse
|
79
|
+
autoload :EmptyMessageBody
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
@@ -10,6 +10,7 @@ describe Grape::Validations do
|
|
10
10
|
def validate_param!(attr_name, params)
|
11
11
|
@option = params[:max].to_i if params.key?(:max)
|
12
12
|
return if params[attr_name].length <= @option
|
13
|
+
|
13
14
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long")
|
14
15
|
end
|
15
16
|
end
|
@@ -11,7 +11,7 @@ describe Grape::Endpoint do
|
|
11
11
|
|
12
12
|
context 'get' do
|
13
13
|
it 'routes to a namespace param with dots' do
|
14
|
-
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[
|
14
|
+
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
|
15
15
|
get '/' do
|
16
16
|
params[:ns_with_dots]
|
17
17
|
end
|
@@ -23,8 +23,8 @@ describe Grape::Endpoint do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'routes to a path with multiple params with dots' do
|
26
|
-
subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[
|
27
|
-
another_id_with_dots: %r{[
|
26
|
+
subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^/]+},
|
27
|
+
another_id_with_dots: %r{[^/]+} } do
|
28
28
|
"#{params[:id_with_dots]}/#{params[:another_id_with_dots]}"
|
29
29
|
end
|
30
30
|
|
@@ -34,9 +34,9 @@ describe Grape::Endpoint do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
it 'routes to namespace and path params with dots, with overridden requirements' do
|
37
|
-
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[
|
38
|
-
get ':another_id_with_dots', requirements: { ns_with_dots: %r{[
|
39
|
-
another_id_with_dots: %r{[
|
37
|
+
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
|
38
|
+
get ':another_id_with_dots', requirements: { ns_with_dots: %r{[^/]+},
|
39
|
+
another_id_with_dots: %r{[^/]+} } do
|
40
40
|
"#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
|
41
41
|
end
|
42
42
|
end
|
@@ -47,8 +47,8 @@ describe Grape::Endpoint do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
it 'routes to namespace and path params with dots, with merged requirements' do
|
50
|
-
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[
|
51
|
-
get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[
|
50
|
+
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
|
51
|
+
get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[^/]+} } do
|
52
52
|
"#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
|
53
53
|
end
|
54
54
|
end
|
@@ -340,19 +340,24 @@ describe Grape::API do
|
|
340
340
|
context 'when the configuration is read within a namespace' do
|
341
341
|
before do
|
342
342
|
a_remounted_api.namespace 'api' do
|
343
|
+
params do
|
344
|
+
requires configuration[:required_param]
|
345
|
+
end
|
343
346
|
get "/#{configuration[:path]}" do
|
344
347
|
'10 votes'
|
345
348
|
end
|
346
349
|
end
|
347
|
-
root_api.mount a_remounted_api, with: { path: 'votes' }
|
348
|
-
root_api.mount a_remounted_api, with: { path: 'scores' }
|
350
|
+
root_api.mount a_remounted_api, with: { path: 'votes', required_param: 'param_key' }
|
351
|
+
root_api.mount a_remounted_api, with: { path: 'scores', required_param: 'param_key' }
|
349
352
|
end
|
350
353
|
|
351
354
|
it 'will use the dynamic configuration on all routes' do
|
352
|
-
get 'api/votes'
|
355
|
+
get 'api/votes', param_key: 'a'
|
353
356
|
expect(last_response.body).to eql '10 votes'
|
354
|
-
get 'api/scores'
|
357
|
+
get 'api/scores', param_key: 'a'
|
355
358
|
expect(last_response.body).to eql '10 votes'
|
359
|
+
get 'api/votes'
|
360
|
+
expect(last_response.status).to eq 400
|
356
361
|
end
|
357
362
|
end
|
358
363
|
|
data/spec/grape/api_spec.rb
CHANGED
@@ -610,6 +610,7 @@ describe Grape::API do
|
|
610
610
|
subject.namespace :example do
|
611
611
|
before do
|
612
612
|
raise 'before filter ran twice' if already_run
|
613
|
+
|
613
614
|
already_run = true
|
614
615
|
header 'X-Custom-Header', 'foo'
|
615
616
|
end
|
@@ -650,12 +651,12 @@ describe Grape::API do
|
|
650
651
|
|
651
652
|
put '/example'
|
652
653
|
expect(last_response.status).to eql 405
|
653
|
-
expect(last_response.body).to eq
|
654
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
655
|
-
<error>
|
656
|
-
|
657
|
-
</error>
|
658
|
-
XML
|
654
|
+
expect(last_response.body).to eq <<~XML
|
655
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
656
|
+
<error>
|
657
|
+
<message>405 Not Allowed</message>
|
658
|
+
</error>
|
659
|
+
XML
|
659
660
|
end
|
660
661
|
end
|
661
662
|
|
@@ -749,6 +750,47 @@ XML
|
|
749
750
|
end
|
750
751
|
end
|
751
752
|
|
753
|
+
describe 'when a resource routes by POST, GET, PATCH, PUT, and DELETE' do
|
754
|
+
before do
|
755
|
+
subject.namespace :example do
|
756
|
+
get do
|
757
|
+
'example'
|
758
|
+
end
|
759
|
+
|
760
|
+
patch do
|
761
|
+
'example'
|
762
|
+
end
|
763
|
+
|
764
|
+
post do
|
765
|
+
'example'
|
766
|
+
end
|
767
|
+
|
768
|
+
delete do
|
769
|
+
'example'
|
770
|
+
end
|
771
|
+
|
772
|
+
put do
|
773
|
+
'example'
|
774
|
+
end
|
775
|
+
end
|
776
|
+
options '/example'
|
777
|
+
end
|
778
|
+
|
779
|
+
describe 'it adds an OPTIONS route for namespaced endpoints that' do
|
780
|
+
it 'returns a 204' do
|
781
|
+
expect(last_response.status).to eql 204
|
782
|
+
end
|
783
|
+
|
784
|
+
it 'has an empty body' do
|
785
|
+
expect(last_response.body).to be_blank
|
786
|
+
end
|
787
|
+
|
788
|
+
it 'has an Allow header' do
|
789
|
+
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, PATCH, POST, DELETE, PUT, HEAD'
|
790
|
+
end
|
791
|
+
end
|
792
|
+
end
|
793
|
+
|
752
794
|
describe 'adds an OPTIONS route for namespaced endpoints that' do
|
753
795
|
before do
|
754
796
|
subject.before { header 'X-Custom-Header', 'foo' }
|
@@ -2106,7 +2148,9 @@ XML
|
|
2106
2148
|
context 'custom errors' do
|
2107
2149
|
before do
|
2108
2150
|
class ConnectionError < RuntimeError; end
|
2151
|
+
|
2109
2152
|
class DatabaseError < RuntimeError; end
|
2153
|
+
|
2110
2154
|
class CommunicationError < StandardError; end
|
2111
2155
|
end
|
2112
2156
|
|
@@ -2262,6 +2306,7 @@ XML
|
|
2262
2306
|
module ApiSpec
|
2263
2307
|
module APIErrors
|
2264
2308
|
class ParentError < StandardError; end
|
2309
|
+
|
2265
2310
|
class ChildError < ParentError; end
|
2266
2311
|
end
|
2267
2312
|
end
|
@@ -3119,10 +3164,10 @@ XML
|
|
3119
3164
|
subject.get 'method'
|
3120
3165
|
|
3121
3166
|
expect(subject.routes.map(&:params)).to eq [{
|
3122
|
-
'group1'
|
3167
|
+
'group1' => { required: true, type: 'Array' },
|
3123
3168
|
'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
|
3124
3169
|
'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
|
3125
|
-
'group2'
|
3170
|
+
'group2' => { required: true, type: 'Array' },
|
3126
3171
|
'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
|
3127
3172
|
'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
|
3128
3173
|
}]
|
@@ -3335,8 +3380,8 @@ XML
|
|
3335
3380
|
mount app
|
3336
3381
|
end
|
3337
3382
|
expect(subject.routes.size).to eq(2)
|
3338
|
-
expect(subject.routes.first.path).to match(%r{
|
3339
|
-
expect(subject.routes.last.path).to match(%r{
|
3383
|
+
expect(subject.routes.first.path).to match(%r{/cool/awesome})
|
3384
|
+
expect(subject.routes.last.path).to match(%r{/cool/sauce})
|
3340
3385
|
end
|
3341
3386
|
|
3342
3387
|
it 'mounts on a path' do
|
@@ -3358,7 +3403,7 @@ XML
|
|
3358
3403
|
APP2.get '/nice' do
|
3359
3404
|
'play'
|
3360
3405
|
end
|
3361
|
-
#
|
3406
|
+
# NOTE: that the reverse won't work, mount from outside-in
|
3362
3407
|
APP3 = subject
|
3363
3408
|
APP3.mount APP1 => '/app1'
|
3364
3409
|
APP1.mount APP2 => '/app2'
|
@@ -3549,6 +3594,7 @@ XML
|
|
3549
3594
|
def self.included(base)
|
3550
3595
|
base.extend(ClassMethods)
|
3551
3596
|
end
|
3597
|
+
|
3552
3598
|
module ClassMethods
|
3553
3599
|
def my_method
|
3554
3600
|
@test = true
|
@@ -3793,12 +3839,12 @@ XML
|
|
3793
3839
|
end
|
3794
3840
|
get '/example'
|
3795
3841
|
expect(last_response.status).to eq(500)
|
3796
|
-
expect(last_response.body).to eq
|
3797
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3798
|
-
<error>
|
3799
|
-
|
3800
|
-
</error>
|
3801
|
-
XML
|
3842
|
+
expect(last_response.body).to eq <<~XML
|
3843
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3844
|
+
<error>
|
3845
|
+
<message>cannot convert String to xml</message>
|
3846
|
+
</error>
|
3847
|
+
XML
|
3802
3848
|
end
|
3803
3849
|
it 'hash' do
|
3804
3850
|
subject.get '/example' do
|
@@ -3809,13 +3855,13 @@ XML
|
|
3809
3855
|
end
|
3810
3856
|
get '/example'
|
3811
3857
|
expect(last_response.status).to eq(200)
|
3812
|
-
expect(last_response.body).to eq
|
3813
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3814
|
-
<hash>
|
3815
|
-
|
3816
|
-
|
3817
|
-
</hash>
|
3818
|
-
XML
|
3858
|
+
expect(last_response.body).to eq <<~XML
|
3859
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3860
|
+
<hash>
|
3861
|
+
<example1>example1</example1>
|
3862
|
+
<example2>example2</example2>
|
3863
|
+
</hash>
|
3864
|
+
XML
|
3819
3865
|
end
|
3820
3866
|
it 'array' do
|
3821
3867
|
subject.get '/example' do
|
@@ -3823,13 +3869,13 @@ XML
|
|
3823
3869
|
end
|
3824
3870
|
get '/example'
|
3825
3871
|
expect(last_response.status).to eq(200)
|
3826
|
-
expect(last_response.body).to eq
|
3827
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3828
|
-
<strings type="array">
|
3829
|
-
|
3830
|
-
|
3831
|
-
</strings>
|
3832
|
-
XML
|
3872
|
+
expect(last_response.body).to eq <<~XML
|
3873
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3874
|
+
<strings type="array">
|
3875
|
+
<string>example1</string>
|
3876
|
+
<string>example2</string>
|
3877
|
+
</strings>
|
3878
|
+
XML
|
3833
3879
|
end
|
3834
3880
|
it 'raised :error from middleware' do
|
3835
3881
|
middleware = Class.new(Grape::Middleware::Base) do
|
@@ -3842,12 +3888,12 @@ XML
|
|
3842
3888
|
end
|
3843
3889
|
get '/'
|
3844
3890
|
expect(last_response.status).to eq(42)
|
3845
|
-
expect(last_response.body).to eq
|
3846
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3847
|
-
<error>
|
3848
|
-
|
3849
|
-
</error>
|
3850
|
-
XML
|
3891
|
+
expect(last_response.body).to eq <<~XML
|
3892
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3893
|
+
<error>
|
3894
|
+
<message>Unauthorized</message>
|
3895
|
+
</error>
|
3896
|
+
XML
|
3851
3897
|
end
|
3852
3898
|
end
|
3853
3899
|
end
|
@@ -4040,6 +4086,49 @@ XML
|
|
4040
4086
|
end
|
4041
4087
|
end
|
4042
4088
|
|
4089
|
+
describe '.inherited' do
|
4090
|
+
context 'overriding within class' do
|
4091
|
+
let(:root_api) do
|
4092
|
+
Class.new(Grape::API) do
|
4093
|
+
@bar = 'Hello, world'
|
4094
|
+
|
4095
|
+
def self.inherited(child_api)
|
4096
|
+
super
|
4097
|
+
child_api.instance_variable_set(:@foo, @bar.dup)
|
4098
|
+
end
|
4099
|
+
end
|
4100
|
+
end
|
4101
|
+
|
4102
|
+
let(:child_api) { Class.new(root_api) }
|
4103
|
+
|
4104
|
+
it 'allows overriding the hook' do
|
4105
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4106
|
+
end
|
4107
|
+
end
|
4108
|
+
|
4109
|
+
context 'overriding via composition' do
|
4110
|
+
module Inherited
|
4111
|
+
def inherited(api)
|
4112
|
+
super
|
4113
|
+
api.instance_variable_set(:@foo, @bar.dup)
|
4114
|
+
end
|
4115
|
+
end
|
4116
|
+
|
4117
|
+
let(:root_api) do
|
4118
|
+
Class.new(Grape::API) do
|
4119
|
+
@bar = 'Hello, world'
|
4120
|
+
extend Inherited
|
4121
|
+
end
|
4122
|
+
end
|
4123
|
+
|
4124
|
+
let(:child_api) { Class.new(root_api) }
|
4125
|
+
|
4126
|
+
it 'allows overriding the hook' do
|
4127
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4128
|
+
end
|
4129
|
+
end
|
4130
|
+
end
|
4131
|
+
|
4043
4132
|
describe 'const_missing' do
|
4044
4133
|
subject(:grape_api) { Class.new(Grape::API) }
|
4045
4134
|
let(:mounted) do
|
@@ -4056,4 +4145,81 @@ XML
|
|
4056
4145
|
expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)
|
4057
4146
|
end
|
4058
4147
|
end
|
4148
|
+
|
4149
|
+
describe 'custom route helpers on nested APIs' do
|
4150
|
+
let(:shared_api_module) do
|
4151
|
+
Module.new do
|
4152
|
+
# rubocop:disable Style/ExplicitBlockArgument because this causes
|
4153
|
+
# the underlying issue in this form
|
4154
|
+
def uniqe_id_route
|
4155
|
+
params do
|
4156
|
+
use :unique_id
|
4157
|
+
end
|
4158
|
+
route_param(:id) do
|
4159
|
+
yield
|
4160
|
+
end
|
4161
|
+
end
|
4162
|
+
# rubocop:enable Style/ExplicitBlockArgument
|
4163
|
+
end
|
4164
|
+
end
|
4165
|
+
let(:shared_api_definitions) do
|
4166
|
+
Module.new do
|
4167
|
+
extend ActiveSupport::Concern
|
4168
|
+
|
4169
|
+
included do
|
4170
|
+
helpers do
|
4171
|
+
params :unique_id do
|
4172
|
+
requires :id, type: String,
|
4173
|
+
allow_blank: false,
|
4174
|
+
regexp: /\d+-\d+/
|
4175
|
+
end
|
4176
|
+
end
|
4177
|
+
end
|
4178
|
+
end
|
4179
|
+
end
|
4180
|
+
let(:orders_root) do
|
4181
|
+
shared = shared_api_definitions
|
4182
|
+
find = orders_find_endpoint
|
4183
|
+
Class.new(Grape::API) do
|
4184
|
+
include shared
|
4185
|
+
|
4186
|
+
namespace(:orders) do
|
4187
|
+
mount find
|
4188
|
+
end
|
4189
|
+
end
|
4190
|
+
end
|
4191
|
+
let(:orders_find_endpoint) do
|
4192
|
+
shared = shared_api_definitions
|
4193
|
+
Class.new(Grape::API) do
|
4194
|
+
include shared
|
4195
|
+
|
4196
|
+
uniqe_id_route do
|
4197
|
+
desc 'Fetch a single order' do
|
4198
|
+
detail 'While specifying the order id on the route'
|
4199
|
+
end
|
4200
|
+
get { params[:id] }
|
4201
|
+
end
|
4202
|
+
end
|
4203
|
+
end
|
4204
|
+
subject(:grape_api) do
|
4205
|
+
Class.new(Grape::API) do
|
4206
|
+
version 'v1', using: :path
|
4207
|
+
end
|
4208
|
+
end
|
4209
|
+
|
4210
|
+
before do
|
4211
|
+
Grape::API::Instance.extend(shared_api_module)
|
4212
|
+
subject.mount orders_root
|
4213
|
+
end
|
4214
|
+
|
4215
|
+
it 'returns an error when the id is bad' do
|
4216
|
+
get '/v1/orders/abc'
|
4217
|
+
expect(last_response.body).to be_eql('id is invalid')
|
4218
|
+
end
|
4219
|
+
|
4220
|
+
it 'returns the given id when it is valid' do
|
4221
|
+
get '/v1/orders/1-2'
|
4222
|
+
expect(last_response.body).to be_eql('1-2')
|
4223
|
+
end
|
4224
|
+
end
|
4059
4225
|
end
|