grape 1.1.0 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -43
  3. data/LICENSE +1 -1
  4. data/README.md +394 -47
  5. data/UPGRADING.md +111 -0
  6. data/grape.gemspec +3 -1
  7. data/lib/grape.rb +98 -66
  8. data/lib/grape/api.rb +136 -175
  9. data/lib/grape/api/instance.rb +280 -0
  10. data/lib/grape/config.rb +32 -0
  11. data/lib/grape/dsl/callbacks.rb +20 -0
  12. data/lib/grape/dsl/desc.rb +39 -7
  13. data/lib/grape/dsl/inside_route.rb +12 -6
  14. data/lib/grape/dsl/middleware.rb +7 -0
  15. data/lib/grape/dsl/parameters.rb +9 -4
  16. data/lib/grape/dsl/routing.rb +5 -1
  17. data/lib/grape/dsl/validations.rb +4 -3
  18. data/lib/grape/eager_load.rb +18 -0
  19. data/lib/grape/endpoint.rb +42 -26
  20. data/lib/grape/error_formatter.rb +1 -1
  21. data/lib/grape/exceptions/base.rb +9 -1
  22. data/lib/grape/exceptions/invalid_response.rb +9 -0
  23. data/lib/grape/exceptions/validation_errors.rb +4 -2
  24. data/lib/grape/formatter.rb +1 -1
  25. data/lib/grape/locale/en.yml +2 -0
  26. data/lib/grape/middleware/auth/base.rb +2 -4
  27. data/lib/grape/middleware/base.rb +2 -0
  28. data/lib/grape/middleware/error.rb +9 -4
  29. data/lib/grape/middleware/helpers.rb +10 -0
  30. data/lib/grape/middleware/stack.rb +1 -1
  31. data/lib/grape/middleware/versioner/header.rb +4 -4
  32. data/lib/grape/parser.rb +1 -1
  33. data/lib/grape/request.rb +1 -1
  34. data/lib/grape/router/attribute_translator.rb +2 -0
  35. data/lib/grape/router/route.rb +2 -2
  36. data/lib/grape/util/base_inheritable.rb +34 -0
  37. data/lib/grape/util/endpoint_configuration.rb +6 -0
  38. data/lib/grape/util/inheritable_values.rb +5 -25
  39. data/lib/grape/util/lazy_block.rb +25 -0
  40. data/lib/grape/util/lazy_value.rb +95 -0
  41. data/lib/grape/util/reverse_stackable_values.rb +7 -36
  42. data/lib/grape/util/stackable_values.rb +19 -22
  43. data/lib/grape/validations/attributes_iterator.rb +5 -3
  44. data/lib/grape/validations/multiple_attributes_iterator.rb +11 -0
  45. data/lib/grape/validations/params_scope.rb +20 -14
  46. data/lib/grape/validations/single_attribute_iterator.rb +13 -0
  47. data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
  48. data/lib/grape/validations/types/file.rb +1 -1
  49. data/lib/grape/validations/validator_factory.rb +6 -11
  50. data/lib/grape/validations/validators/all_or_none.rb +6 -13
  51. data/lib/grape/validations/validators/as.rb +2 -3
  52. data/lib/grape/validations/validators/at_least_one_of.rb +5 -13
  53. data/lib/grape/validations/validators/base.rb +11 -10
  54. data/lib/grape/validations/validators/coerce.rb +4 -0
  55. data/lib/grape/validations/validators/default.rb +1 -1
  56. data/lib/grape/validations/validators/exactly_one_of.rb +6 -23
  57. data/lib/grape/validations/validators/multiple_params_base.rb +14 -10
  58. data/lib/grape/validations/validators/mutual_exclusion.rb +6 -18
  59. data/lib/grape/validations/validators/same_as.rb +23 -0
  60. data/lib/grape/version.rb +1 -1
  61. data/spec/grape/api/defines_boolean_in_params_spec.rb +37 -0
  62. data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
  63. data/spec/grape/api_remount_spec.rb +466 -0
  64. data/spec/grape/api_spec.rb +379 -1
  65. data/spec/grape/config_spec.rb +17 -0
  66. data/spec/grape/dsl/desc_spec.rb +40 -16
  67. data/spec/grape/dsl/middleware_spec.rb +8 -0
  68. data/spec/grape/dsl/routing_spec.rb +10 -0
  69. data/spec/grape/endpoint_spec.rb +40 -4
  70. data/spec/grape/exceptions/base_spec.rb +65 -0
  71. data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
  72. data/spec/grape/exceptions/validation_errors_spec.rb +6 -4
  73. data/spec/grape/integration/rack_spec.rb +22 -6
  74. data/spec/grape/middleware/auth/dsl_spec.rb +3 -3
  75. data/spec/grape/middleware/base_spec.rb +8 -0
  76. data/spec/grape/middleware/exception_spec.rb +1 -1
  77. data/spec/grape/middleware/formatter_spec.rb +15 -5
  78. data/spec/grape/middleware/versioner/header_spec.rb +6 -0
  79. data/spec/grape/named_api_spec.rb +19 -0
  80. data/spec/grape/request_spec.rb +24 -0
  81. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +29 -0
  82. data/spec/grape/validations/params_scope_spec.rb +184 -8
  83. data/spec/grape/validations/single_attribute_iterator_spec.rb +33 -0
  84. data/spec/grape/validations/validators/all_or_none_spec.rb +138 -30
  85. data/spec/grape/validations/validators/at_least_one_of_spec.rb +173 -29
  86. data/spec/grape/validations/validators/coerce_spec.rb +10 -2
  87. data/spec/grape/validations/validators/exactly_one_of_spec.rb +202 -38
  88. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +184 -27
  89. data/spec/grape/validations/validators/same_as_spec.rb +63 -0
  90. data/spec/grape/validations_spec.rb +33 -21
  91. data/spec/spec_helper.rb +4 -1
  92. metadata +35 -23
  93. data/Appraisals +0 -32
  94. data/Dangerfile +0 -2
  95. data/Gemfile +0 -33
  96. data/Gemfile.lock +0 -231
  97. data/Guardfile +0 -10
  98. data/RELEASING.md +0 -111
  99. data/Rakefile +0 -25
  100. data/benchmark/simple.rb +0 -27
  101. data/benchmark/simple_with_type_coercer.rb +0 -22
  102. data/gemfiles/multi_json.gemfile +0 -35
  103. data/gemfiles/multi_xml.gemfile +0 -35
  104. data/gemfiles/rack_1.5.2.gemfile +0 -35
  105. data/gemfiles/rack_edge.gemfile +0 -35
  106. data/gemfiles/rails_3.gemfile +0 -36
  107. data/gemfiles/rails_4.gemfile +0 -35
  108. data/gemfiles/rails_5.gemfile +0 -35
  109. data/gemfiles/rails_edge.gemfile +0 -35
  110. data/pkg/grape-0.17.0.gem +0 -0
  111. data/pkg/grape-0.19.0.gem +0 -0
@@ -220,6 +220,15 @@ describe Grape::API do
220
220
  end
221
221
  end
222
222
 
223
+ describe '.call' do
224
+ context 'it does not add to the app setup' do
225
+ it 'calls the app' do
226
+ expect(subject).not_to receive(:add_setup)
227
+ subject.call({})
228
+ end
229
+ end
230
+ end
231
+
223
232
  describe '.route_param' do
224
233
  it 'adds a parameterized route segment namespace' do
225
234
  subject.namespace :users do
@@ -876,6 +885,40 @@ XML
876
885
  end
877
886
  end
878
887
 
888
+ describe '.compile!' do
889
+ it 'requires the grape/eager_load file' do
890
+ expect(app).to receive(:require).with('grape/eager_load') { nil }
891
+ app.compile!
892
+ end
893
+
894
+ it 'compiles the instance for rack!' do
895
+ stubbed_object = double(:instance_for_rack)
896
+ allow(app).to receive(:instance_for_rack) { stubbed_object }
897
+ end
898
+ end
899
+
900
+ # NOTE: this method is required to preserve the ability of pre-mounting
901
+ # the root API into a namespace, it may be deprecated in the future.
902
+ describe 'instance_for_rack' do
903
+ context 'when the app was not mounted' do
904
+ it 'returns the base_instance' do
905
+ expect(app.send(:instance_for_rack)).to eq app.base_instance
906
+ end
907
+ end
908
+
909
+ context 'when the app was mounted' do
910
+ it 'returns the first mounted instance' do
911
+ mounted_app = app
912
+ Class.new(Grape::API) do
913
+ namespace 'new_namespace' do
914
+ mount mounted_app
915
+ end
916
+ end
917
+ expect(app.send(:instance_for_rack)).to eq app.send(:mounted_instances).first
918
+ end
919
+ end
920
+ end
921
+
879
922
  describe 'filters' do
880
923
  it 'adds a before filter' do
881
924
  subject.before { @foo = 'first' }
@@ -1348,6 +1391,28 @@ XML
1348
1391
  end
1349
1392
  end
1350
1393
 
1394
+ describe '.insert' do
1395
+ it 'inserts middleware in a specific location in the stack' do
1396
+ m = Class.new(Grape::Middleware::Base) do
1397
+ def call(env)
1398
+ env['phony.args'] ||= []
1399
+ env['phony.args'] << @options[:message]
1400
+ @app.call(env)
1401
+ end
1402
+ end
1403
+
1404
+ subject.use ApiSpec::PhonyMiddleware, 'bye'
1405
+ subject.insert 0, m, message: 'good'
1406
+ subject.insert 0, m, message: 'hello'
1407
+ subject.get '/' do
1408
+ env['phony.args'].join(' ')
1409
+ end
1410
+
1411
+ get '/'
1412
+ expect(last_response.body).to eql 'hello good bye'
1413
+ end
1414
+ end
1415
+
1351
1416
  describe '.http_basic' do
1352
1417
  it 'protects any resources on the same scope' do
1353
1418
  subject.http_basic do |u, _p|
@@ -1597,6 +1662,199 @@ XML
1597
1662
  end
1598
1663
  end
1599
1664
 
1665
+ describe 'lifecycle' do
1666
+ let!(:lifecycle) { [] }
1667
+ let!(:standard_cycle) do
1668
+ %i[before before_validation after_validation api_call after finally]
1669
+ end
1670
+
1671
+ let!(:validation_error) do
1672
+ %i[before before_validation finally]
1673
+ end
1674
+
1675
+ let!(:errored_cycle) do
1676
+ %i[before before_validation after_validation api_call finally]
1677
+ end
1678
+
1679
+ before do
1680
+ current_cycle = lifecycle
1681
+
1682
+ subject.before do
1683
+ current_cycle << :before
1684
+ end
1685
+
1686
+ subject.before_validation do
1687
+ current_cycle << :before_validation
1688
+ end
1689
+
1690
+ subject.after_validation do
1691
+ current_cycle << :after_validation
1692
+ end
1693
+
1694
+ subject.after do
1695
+ current_cycle << :after
1696
+ end
1697
+
1698
+ subject.finally do
1699
+ current_cycle << :finally
1700
+ end
1701
+ end
1702
+
1703
+ context 'when the api_call succeeds' do
1704
+ before do
1705
+ current_cycle = lifecycle
1706
+
1707
+ subject.get 'api_call' do
1708
+ current_cycle << :api_call
1709
+ end
1710
+ end
1711
+
1712
+ it 'follows the standard life_cycle' do
1713
+ get '/api_call'
1714
+ expect(lifecycle).to eq standard_cycle
1715
+ end
1716
+ end
1717
+
1718
+ context 'when the api_call has a controlled error' do
1719
+ before do
1720
+ current_cycle = lifecycle
1721
+
1722
+ subject.get 'api_call' do
1723
+ current_cycle << :api_call
1724
+ error!(:some_error)
1725
+ end
1726
+ end
1727
+
1728
+ it 'follows the errored life_cycle (skips after)' do
1729
+ get '/api_call'
1730
+ expect(lifecycle).to eq errored_cycle
1731
+ end
1732
+ end
1733
+
1734
+ context 'when the api_call has an exception' do
1735
+ before do
1736
+ current_cycle = lifecycle
1737
+
1738
+ subject.get 'api_call' do
1739
+ current_cycle << :api_call
1740
+ raise StandardError
1741
+ end
1742
+ end
1743
+
1744
+ it 'follows the errored life_cycle (skips after)' do
1745
+ expect { get '/api_call' }.to raise_error(StandardError)
1746
+ expect(lifecycle).to eq errored_cycle
1747
+ end
1748
+ end
1749
+
1750
+ context 'when the api_call fails validation' do
1751
+ before do
1752
+ current_cycle = lifecycle
1753
+
1754
+ subject.params do
1755
+ requires :some_param, type: String
1756
+ end
1757
+
1758
+ subject.get 'api_call' do
1759
+ current_cycle << :api_call
1760
+ end
1761
+ end
1762
+
1763
+ it 'follows the failed_validation cycle (skips after_validation, api_call & after)' do
1764
+ get '/api_call'
1765
+ expect(lifecycle).to eq validation_error
1766
+ end
1767
+ end
1768
+ end
1769
+
1770
+ describe '.finally' do
1771
+ let!(:code) { { has_executed: false } }
1772
+ let(:block_to_run) do
1773
+ code_to_execute = code
1774
+ proc do
1775
+ code_to_execute[:has_executed] = true
1776
+ end
1777
+ end
1778
+
1779
+ context 'when the ensure block has no exceptions' do
1780
+ before { subject.finally(&block_to_run) }
1781
+
1782
+ context 'when no API call is made' do
1783
+ it 'has not executed the ensure code' do
1784
+ expect(code[:has_executed]).to be false
1785
+ end
1786
+ end
1787
+
1788
+ context 'when no errors occurs' do
1789
+ before do
1790
+ subject.get '/no_exceptions' do
1791
+ 'success'
1792
+ end
1793
+ end
1794
+
1795
+ it 'executes the ensure code' do
1796
+ get '/no_exceptions'
1797
+ expect(last_response.body).to eq 'success'
1798
+ expect(code[:has_executed]).to be true
1799
+ end
1800
+
1801
+ context 'with a helper' do
1802
+ let(:block_to_run) do
1803
+ code_to_execute = code
1804
+ proc do
1805
+ code_to_execute[:value] = some_helper
1806
+ end
1807
+ end
1808
+
1809
+ before do
1810
+ subject.helpers do
1811
+ def some_helper
1812
+ 'some_value'
1813
+ end
1814
+ end
1815
+
1816
+ subject.get '/with_helpers' do
1817
+ 'success'
1818
+ end
1819
+ end
1820
+
1821
+ it 'has access to the helper' do
1822
+ get '/with_helpers'
1823
+ expect(code[:value]).to eq 'some_value'
1824
+ end
1825
+ end
1826
+ end
1827
+
1828
+ context 'when an unhandled occurs inside the API call' do
1829
+ before do
1830
+ subject.get '/unhandled_exception' do
1831
+ raise StandardError
1832
+ end
1833
+ end
1834
+
1835
+ it 'executes the ensure code' do
1836
+ expect { get '/unhandled_exception' }.to raise_error StandardError
1837
+ expect(code[:has_executed]).to be true
1838
+ end
1839
+ end
1840
+
1841
+ context 'when a handled error occurs inside the API call' do
1842
+ before do
1843
+ subject.rescue_from(StandardError) { error! 'handled' }
1844
+ subject.get '/handled_exception' do
1845
+ raise StandardError
1846
+ end
1847
+ end
1848
+
1849
+ it 'executes the ensure code' do
1850
+ get '/handled_exception'
1851
+ expect(code[:has_executed]).to be true
1852
+ expect(last_response.body).to eq 'handled'
1853
+ end
1854
+ end
1855
+ end
1856
+ end
1857
+
1600
1858
  describe '.rescue_from' do
1601
1859
  it 'does not rescue errors when rescue_from is not set' do
1602
1860
  subject.get '/exception' do
@@ -1723,6 +1981,16 @@ XML
1723
1981
  expect(last_response.status).to eql 500
1724
1982
  expect(last_response.body).to eq('Formatter Error')
1725
1983
  end
1984
+
1985
+ it 'uses default_rescue_handler to handle invalid response from rescue_from' do
1986
+ subject.rescue_from(:all) { 'error' }
1987
+ subject.get('/') { raise }
1988
+
1989
+ expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
1990
+ get '/'
1991
+ expect(last_response.status).to eql 500
1992
+ expect(last_response.body).to eql 'Invalid response'
1993
+ end
1726
1994
  end
1727
1995
 
1728
1996
  describe '.rescue_from klass, block' do
@@ -3177,6 +3445,43 @@ XML
3177
3445
  expect { a.mount b }.to_not raise_error
3178
3446
  end
3179
3447
  end
3448
+
3449
+ context 'when including a module' do
3450
+ let(:included_module) do
3451
+ Module.new do
3452
+ def self.included(base)
3453
+ base.extend(ClassMethods)
3454
+ end
3455
+ module ClassMethods
3456
+ def my_method
3457
+ @test = true
3458
+ end
3459
+ end
3460
+ end
3461
+ end
3462
+
3463
+ it 'should correctly include module in nested mount' do
3464
+ module_to_include = included_module
3465
+ v1 = Class.new(Grape::API) do
3466
+ version :v1, using: :path
3467
+ include module_to_include
3468
+ my_method
3469
+ end
3470
+ v2 = Class.new(Grape::API) do
3471
+ version :v2, using: :path
3472
+ end
3473
+ segment_base = Class.new(Grape::API) do
3474
+ mount v1
3475
+ mount v2
3476
+ end
3477
+
3478
+ Class.new(Grape::API) do
3479
+ mount segment_base
3480
+ end
3481
+
3482
+ expect(v1.my_method).to be_truthy
3483
+ end
3484
+ end
3180
3485
  end
3181
3486
  end
3182
3487
 
@@ -3192,7 +3497,7 @@ XML
3192
3497
  it 'sets the instance' do
3193
3498
  expect(subject.instance).to be_nil
3194
3499
  subject.compile
3195
- expect(subject.instance).to be_kind_of(subject)
3500
+ expect(subject.instance).to be_kind_of(subject.base_instance)
3196
3501
  end
3197
3502
  end
3198
3503
 
@@ -3449,6 +3754,44 @@ XML
3449
3754
  end
3450
3755
  end
3451
3756
 
3757
+ describe '.configure' do
3758
+ context 'when given a block' do
3759
+ it 'returns self' do
3760
+ expect(subject.configure {}).to be subject
3761
+ end
3762
+
3763
+ it 'calls the block passing the config' do
3764
+ call = [false, nil]
3765
+ subject.configure do |config|
3766
+ call = [true, config]
3767
+ end
3768
+
3769
+ expect(call[0]).to be true
3770
+ expect(call[1]).not_to be_nil
3771
+ end
3772
+ end
3773
+
3774
+ context 'when not given a block' do
3775
+ it 'returns a configuration object' do
3776
+ expect(subject.configure).to respond_to(:[], :[]=)
3777
+ end
3778
+ end
3779
+
3780
+ it 'allows configuring the api' do
3781
+ subject.configure do |config|
3782
+ config[:hello] = 'hello'
3783
+ config[:bread] = 'bread'
3784
+ end
3785
+
3786
+ subject.get '/hello-bread' do
3787
+ "#{configuration[:hello]} #{configuration[:bread]}"
3788
+ end
3789
+
3790
+ get '/hello-bread'
3791
+ expect(last_response.body).to eq 'hello bread'
3792
+ end
3793
+ end
3794
+
3452
3795
  context 'catch-all' do
3453
3796
  before do
3454
3797
  api1 = Class.new(Grape::API)
@@ -3580,4 +3923,39 @@ XML
3580
3923
  end
3581
3924
  end
3582
3925
  end
3926
+
3927
+ describe 'normal class methods' do
3928
+ subject(:grape_api) { Class.new(Grape::API) }
3929
+
3930
+ before do
3931
+ stub_const('MyAPI', grape_api)
3932
+ end
3933
+
3934
+ it 'can find the appropiate name' do
3935
+ expect(grape_api.name).to eq 'MyAPI'
3936
+ end
3937
+
3938
+ it 'is equal to itself' do
3939
+ expect(grape_api.itself).to eq grape_api
3940
+ expect(grape_api).to eq MyAPI
3941
+ expect(grape_api.eql?(MyAPI))
3942
+ end
3943
+ end
3944
+
3945
+ describe 'const_missing' do
3946
+ subject(:grape_api) { Class.new(Grape::API) }
3947
+ let(:mounted) do
3948
+ Class.new(Grape::API) do
3949
+ get '/missing' do
3950
+ SomeRandomConstant
3951
+ end
3952
+ end
3953
+ end
3954
+
3955
+ before { subject.mount mounted => '/const' }
3956
+
3957
+ it 'raises an error' do
3958
+ expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)
3959
+ end
3960
+ end
3583
3961
  end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe '.configure' do
4
+ before do
5
+ Grape.configure do |config|
6
+ config.param_builder = 42
7
+ end
8
+ end
9
+
10
+ after do
11
+ Grape.config.reset
12
+ end
13
+
14
+ it 'is configured to the new value' do
15
+ expect(Grape.config.param_builder).to eq 42
16
+ end
17
+ end