grape 1.1.0 → 1.2.5

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.
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