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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -2
  3. data/README.md +43 -10
  4. data/UPGRADING.md +91 -0
  5. data/grape.gemspec +5 -5
  6. data/lib/grape/api/instance.rb +13 -17
  7. data/lib/grape/api.rb +7 -14
  8. data/lib/grape/cookies.rb +2 -0
  9. data/lib/grape/dsl/callbacks.rb +1 -1
  10. data/lib/grape/dsl/desc.rb +3 -5
  11. data/lib/grape/dsl/helpers.rb +6 -4
  12. data/lib/grape/dsl/inside_route.rb +18 -9
  13. data/lib/grape/dsl/middleware.rb +4 -4
  14. data/lib/grape/dsl/parameters.rb +11 -7
  15. data/lib/grape/dsl/request_response.rb +9 -6
  16. data/lib/grape/dsl/routing.rb +7 -6
  17. data/lib/grape/dsl/settings.rb +5 -5
  18. data/lib/grape/endpoint.rb +21 -36
  19. data/lib/grape/error_formatter/json.rb +2 -6
  20. data/lib/grape/error_formatter/xml.rb +2 -6
  21. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  22. data/lib/grape/exceptions/validation.rb +2 -3
  23. data/lib/grape/exceptions/validation_errors.rb +1 -1
  24. data/lib/grape/formatter/json.rb +1 -0
  25. data/lib/grape/formatter/serializable_hash.rb +2 -1
  26. data/lib/grape/formatter/xml.rb +1 -0
  27. data/lib/grape/locale/en.yml +1 -1
  28. data/lib/grape/middleware/auth/base.rb +3 -3
  29. data/lib/grape/middleware/base.rb +4 -2
  30. data/lib/grape/middleware/error.rb +1 -1
  31. data/lib/grape/middleware/formatter.rb +4 -4
  32. data/lib/grape/middleware/stack.rb +10 -16
  33. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  34. data/lib/grape/middleware/versioner/header.rb +6 -4
  35. data/lib/grape/middleware/versioner/param.rb +1 -0
  36. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  37. data/lib/grape/middleware/versioner/path.rb +2 -0
  38. data/lib/grape/parser/json.rb +1 -1
  39. data/lib/grape/parser/xml.rb +1 -1
  40. data/lib/grape/path.rb +1 -0
  41. data/lib/grape/request.rb +3 -0
  42. data/lib/grape/router/attribute_translator.rb +1 -1
  43. data/lib/grape/router/pattern.rb +1 -1
  44. data/lib/grape/router/route.rb +2 -2
  45. data/lib/grape/router.rb +6 -0
  46. data/lib/grape/util/inheritable_setting.rb +1 -3
  47. data/lib/grape/util/lazy_value.rb +3 -2
  48. data/lib/grape/validations/attributes_iterator.rb +8 -0
  49. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  50. data/lib/grape/validations/params_scope.rb +92 -58
  51. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  52. data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
  53. data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
  54. data/lib/grape/validations/types/invalid_value.rb +24 -0
  55. data/lib/grape/validations/types/json.rb +2 -1
  56. data/lib/grape/validations/types/primitive_coercer.rb +3 -3
  57. data/lib/grape/validations/types.rb +1 -4
  58. data/lib/grape/validations/validator_factory.rb +1 -1
  59. data/lib/grape/validations/validators/all_or_none.rb +1 -0
  60. data/lib/grape/validations/validators/as.rb +4 -8
  61. data/lib/grape/validations/validators/at_least_one_of.rb +1 -0
  62. data/lib/grape/validations/validators/base.rb +12 -7
  63. data/lib/grape/validations/validators/coerce.rb +8 -9
  64. data/lib/grape/validations/validators/default.rb +1 -0
  65. data/lib/grape/validations/validators/exactly_one_of.rb +1 -0
  66. data/lib/grape/validations/validators/multiple_params_base.rb +5 -2
  67. data/lib/grape/validations/validators/mutual_exclusion.rb +1 -0
  68. data/lib/grape/validations/validators/presence.rb +1 -0
  69. data/lib/grape/validations/validators/regexp.rb +1 -0
  70. data/lib/grape/validations/validators/same_as.rb +1 -0
  71. data/lib/grape/validations/validators/values.rb +3 -0
  72. data/lib/grape/version.rb +1 -1
  73. data/lib/grape.rb +3 -1
  74. data/spec/grape/api/custom_validations_spec.rb +1 -0
  75. data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
  76. data/spec/grape/api_remount_spec.rb +9 -4
  77. data/spec/grape/api_spec.rb +203 -37
  78. data/spec/grape/dsl/callbacks_spec.rb +1 -1
  79. data/spec/grape/dsl/middleware_spec.rb +1 -1
  80. data/spec/grape/dsl/parameters_spec.rb +1 -0
  81. data/spec/grape/dsl/routing_spec.rb +1 -1
  82. data/spec/grape/endpoint/declared_spec.rb +259 -1
  83. data/spec/grape/endpoint_spec.rb +18 -5
  84. data/spec/grape/entity_spec.rb +10 -10
  85. data/spec/grape/middleware/auth/dsl_spec.rb +1 -1
  86. data/spec/grape/middleware/error_spec.rb +1 -2
  87. data/spec/grape/middleware/formatter_spec.rb +2 -2
  88. data/spec/grape/middleware/stack_spec.rb +4 -3
  89. data/spec/grape/request_spec.rb +1 -1
  90. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  91. data/spec/grape/validations/params_scope_spec.rb +37 -3
  92. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  93. data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
  94. data/spec/grape/validations/validators/coerce_spec.rb +129 -22
  95. data/spec/grape/validations/validators/except_values_spec.rb +2 -2
  96. data/spec/grape/validations/validators/values_spec.rb +15 -11
  97. data/spec/grape/validations_spec.rb +280 -0
  98. data/spec/shared/versioning_examples.rb +22 -22
  99. data/spec/spec_helper.rb +1 -1
  100. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  101. data/spec/support/versioned_helpers.rb +1 -1
  102. metadata +8 -6
@@ -16,6 +16,7 @@ describe Grape::Endpoint do
16
16
  requires :first
17
17
  optional :second
18
18
  optional :third, default: 'third-default'
19
+ optional :multiple_types, types: [Integer, String]
19
20
  optional :nested, type: Hash do
20
21
  optional :fourth
21
22
  optional :fifth
@@ -96,6 +97,16 @@ describe Grape::Endpoint do
96
97
  expect(JSON.parse(last_response.body)['nested']['fourth']).to be_nil
97
98
  end
98
99
 
100
+ it 'should show nil for multiple allowed types if include_missing is true' do
101
+ subject.get '/declared' do
102
+ declared(params, include_missing: true)
103
+ end
104
+
105
+ get '/declared?first=present'
106
+ expect(last_response.status).to eq(200)
107
+ expect(JSON.parse(last_response.body)['multiple_types']).to be_nil
108
+ end
109
+
99
110
  it 'does not work in a before filter' do
100
111
  subject.before do
101
112
  declared(params)
@@ -113,7 +124,7 @@ describe Grape::Endpoint do
113
124
  end
114
125
  get '/declared?first=present'
115
126
  expect(last_response.status).to eq(200)
116
- expect(JSON.parse(last_response.body).keys.size).to eq(10)
127
+ expect(JSON.parse(last_response.body).keys.size).to eq(11)
117
128
  end
118
129
 
119
130
  it 'has a optional param with default value all the time' do
@@ -587,4 +598,251 @@ describe Grape::Endpoint do
587
598
  expect(json.key?(:artist_id)).not_to be_truthy
588
599
  end
589
600
  end
601
+
602
+ describe 'parameter renaming' do
603
+ context 'with a deeply nested parameter structure' do
604
+ let(:params) do
605
+ {
606
+ i_a: 'a',
607
+ i_b: {
608
+ i_c: 'c',
609
+ i_d: {
610
+ i_e: {
611
+ i_f: 'f',
612
+ i_g: 'g',
613
+ i_h: [
614
+ {
615
+ i_ha: 'ha1',
616
+ i_hb: {
617
+ i_hc: 'c'
618
+ }
619
+ },
620
+ {
621
+ i_ha: 'ha2',
622
+ i_hb: {
623
+ i_hc: 'c'
624
+ }
625
+ }
626
+ ]
627
+ }
628
+ }
629
+ }
630
+ }
631
+ end
632
+ let(:declared) do
633
+ {
634
+ o_a: 'a',
635
+ o_b: {
636
+ o_c: 'c',
637
+ o_d: {
638
+ o_e: {
639
+ o_f: 'f',
640
+ o_g: 'g',
641
+ o_h: [
642
+ {
643
+ o_ha: 'ha1',
644
+ o_hb: {
645
+ o_hc: 'c'
646
+ }
647
+ },
648
+ {
649
+ o_ha: 'ha2',
650
+ o_hb: {
651
+ o_hc: 'c'
652
+ }
653
+ }
654
+ ]
655
+ }
656
+ }
657
+ }
658
+ }
659
+ end
660
+ let(:params_keys) do
661
+ [
662
+ 'i_a',
663
+ 'i_b',
664
+ 'i_b[i_c]',
665
+ 'i_b[i_d]',
666
+ 'i_b[i_d][i_e]',
667
+ 'i_b[i_d][i_e][i_f]',
668
+ 'i_b[i_d][i_e][i_g]',
669
+ 'i_b[i_d][i_e][i_h]',
670
+ 'i_b[i_d][i_e][i_h][i_ha]',
671
+ 'i_b[i_d][i_e][i_h][i_hb]',
672
+ 'i_b[i_d][i_e][i_h][i_hb][i_hc]'
673
+ ]
674
+ end
675
+
676
+ before do
677
+ subject.format :json
678
+ subject.params do
679
+ optional :i_a, type: String, as: :o_a
680
+ optional :i_b, type: Hash, as: :o_b do
681
+ optional :i_c, type: String, as: :o_c
682
+ optional :i_d, type: Hash, as: :o_d do
683
+ optional :i_e, type: Hash, as: :o_e do
684
+ optional :i_f, type: String, as: :o_f
685
+ optional :i_g, type: String, as: :o_g
686
+ optional :i_h, type: Array, as: :o_h do
687
+ optional :i_ha, type: String, as: :o_ha
688
+ optional :i_hb, type: Hash, as: :o_hb do
689
+ optional :i_hc, type: String, as: :o_hc
690
+ end
691
+ end
692
+ end
693
+ end
694
+ end
695
+ end
696
+ subject.post '/test' do
697
+ declared(params, include_missing: false)
698
+ end
699
+ subject.post '/test/no-mod' do
700
+ before = params.to_h
701
+ declared(params, include_missing: false)
702
+ after = params.to_h
703
+ { before: before, after: after }
704
+ end
705
+ end
706
+
707
+ it 'generates the correct parameter names for documentation' do
708
+ expect(subject.routes.first.params.keys).to match(params_keys)
709
+ end
710
+
711
+ it 'maps the renamed parameter correctly' do
712
+ post '/test', **params
713
+ expect(JSON.parse(last_response.body, symbolize_names: true)).to \
714
+ match(declared)
715
+ end
716
+
717
+ it 'maps no parameters when none are given' do
718
+ post '/test'
719
+ expect(JSON.parse(last_response.body)).to match({})
720
+ end
721
+
722
+ it 'does not modify the request params' do
723
+ post '/test/no-mod', **params
724
+ result = JSON.parse(last_response.body, symbolize_names: true)
725
+ expect(result[:before]).to match(result[:after])
726
+ end
727
+ end
728
+
729
+ context 'with a renamed root parameter' do
730
+ before do
731
+ subject.format :json
732
+ subject.params do
733
+ optional :email_address, type: String, regexp: /.+@.+/, as: :email
734
+ end
735
+ subject.post '/test' do
736
+ declared(params, include_missing: false)
737
+ end
738
+ end
739
+
740
+ it 'generates the correct parameter names for documentation' do
741
+ expect(subject.routes.first.params.keys).to match(%w[email_address])
742
+ end
743
+
744
+ it 'maps the renamed parameter correctly (original name)' do
745
+ post '/test', email_address: 'test@example.com'
746
+ expect(JSON.parse(last_response.body)).to \
747
+ match('email' => 'test@example.com')
748
+ end
749
+
750
+ it 'validates the renamed parameter correctly (original name)' do
751
+ post '/test', email_address: 'bad[at]example.com'
752
+ expect(JSON.parse(last_response.body)).to \
753
+ match('error' => 'email_address is invalid')
754
+ end
755
+
756
+ it 'ignores the renamed parameter (as name)' do
757
+ post '/test', email: 'test@example.com'
758
+ expect(JSON.parse(last_response.body)).to match({})
759
+ end
760
+ end
761
+
762
+ context 'with a renamed hash with nested parameters' do
763
+ before do
764
+ subject.format :json
765
+ subject.params do
766
+ optional :address, type: Hash, as: :address_attributes do
767
+ optional :street, type: String, values: ['Street 1', 'Street 2'],
768
+ default: 'Street 1'
769
+ optional :city, type: String
770
+ end
771
+ end
772
+ subject.post '/test' do
773
+ declared(params, include_missing: false)
774
+ end
775
+ end
776
+
777
+ it 'generates the correct parameter names for documentation' do
778
+ expect(subject.routes.first.params.keys).to \
779
+ match(%w[address address[street] address[city]])
780
+ end
781
+
782
+ it 'maps the renamed parameter correctly (original name)' do
783
+ post '/test', address: { city: 'Berlin', street: 'Street 2', t: 't' }
784
+ expect(JSON.parse(last_response.body)).to \
785
+ match('address_attributes' => { 'city' => 'Berlin',
786
+ 'street' => 'Street 2' })
787
+ end
788
+
789
+ it 'validates the renamed parameter correctly (original name)' do
790
+ post '/test', address: { street: 'unknown' }
791
+ expect(JSON.parse(last_response.body)).to \
792
+ match('error' => 'address[street] does not have a valid value')
793
+ end
794
+
795
+ it 'ignores the renamed parameter (as name)' do
796
+ post '/test', address_attributes: { city: 'Berlin', unknown: '1' }
797
+ expect(JSON.parse(last_response.body)).to match({})
798
+ end
799
+ end
800
+
801
+ context 'with a renamed hash with nested renamed parameter' do
802
+ before do
803
+ subject.format :json
804
+ subject.params do
805
+ optional :user, type: Hash, as: :user_attributes do
806
+ optional :email_address, type: String, regexp: /.+@.+/, as: :email
807
+ end
808
+ end
809
+ subject.post '/test' do
810
+ declared(params, include_missing: false)
811
+ end
812
+ end
813
+
814
+ it 'generates the correct parameter names for documentation' do
815
+ expect(subject.routes.first.params.keys).to \
816
+ match(%w[user user[email_address]])
817
+ end
818
+
819
+ it 'maps the renamed parameter correctly (original name)' do
820
+ post '/test', user: { email_address: 'test@example.com' }
821
+ expect(JSON.parse(last_response.body)).to \
822
+ match('user_attributes' => { 'email' => 'test@example.com' })
823
+ end
824
+
825
+ it 'validates the renamed parameter correctly (original name)' do
826
+ post '/test', user: { email_address: 'bad[at]example.com' }
827
+ expect(JSON.parse(last_response.body)).to \
828
+ match('error' => 'user[email_address] is invalid')
829
+ end
830
+
831
+ it 'ignores the renamed parameter (as name, 1)' do
832
+ post '/test', user: { email: 'test@example.com' }
833
+ expect(JSON.parse(last_response.body)).to \
834
+ match({ 'user_attributes' => {} })
835
+ end
836
+
837
+ it 'ignores the renamed parameter (as name, 2)' do
838
+ post '/test', user_attributes: { email_address: 'test@example.com' }
839
+ expect(JSON.parse(last_response.body)).to match({})
840
+ end
841
+
842
+ it 'ignores the renamed parameter (as name, 3)' do
843
+ post '/test', user_attributes: { email: 'test@example.com' }
844
+ expect(JSON.parse(last_response.body)).to match({})
845
+ end
846
+ end
847
+ end
590
848
  end
@@ -150,7 +150,7 @@ describe Grape::Endpoint do
150
150
  end
151
151
  it 'includes headers passed as symbols' do
152
152
  env = Rack::MockRequest.env_for('/headers')
153
- env['HTTP_SYMBOL_HEADER'.to_sym] = 'Goliath passes symbols'
153
+ env[:HTTP_SYMBOL_HEADER] = 'Goliath passes symbols'
154
154
  body = read_chunks(subject.call(env)[2]).join
155
155
  expect(JSON.parse(body)['Symbol-Header']).to eq('Goliath passes symbols')
156
156
  end
@@ -212,10 +212,10 @@ describe Grape::Endpoint do
212
212
  end
213
213
  get '/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2'
214
214
  expect(last_response.body).to eq('3')
215
- cookies = Hash[last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
215
+ cookies = last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
216
216
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
217
217
  [cookie.name, cookie]
218
- end]
218
+ end.to_h
219
219
  expect(cookies.size).to eq(2)
220
220
  %w[and_this delete_this_cookie].each do |cookie_name|
221
221
  cookie = cookies[cookie_name]
@@ -236,10 +236,10 @@ describe Grape::Endpoint do
236
236
  end
237
237
  get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
238
238
  expect(last_response.body).to eq('3')
239
- cookies = Hash[last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
239
+ cookies = last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
240
240
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
241
241
  [cookie.name, cookie]
242
- end]
242
+ end.to_h
243
243
  expect(cookies.size).to eq(2)
244
244
  %w[and_this delete_this_cookie].each do |cookie_name|
245
245
  cookie = cookies[cookie_name]
@@ -420,6 +420,19 @@ describe Grape::Endpoint do
420
420
  expect(last_response.status).to eq(201)
421
421
  expect(last_response.body).to eq('Bob')
422
422
  end
423
+
424
+ # Rack swallowed this error until v2.2.0
425
+ it 'returns a 400 if given an invalid multipart body', if: Gem::Version.new(Rack.release) >= Gem::Version.new('2.2.0') do
426
+ subject.params do
427
+ requires :file, type: Rack::Multipart::UploadedFile
428
+ end
429
+ subject.post '/upload' do
430
+ params[:file][:filename]
431
+ end
432
+ post '/upload', { file: '' }, 'CONTENT_TYPE' => 'multipart/form-data; boundary=foobar'
433
+ expect(last_response.status).to eq(400)
434
+ expect(last_response.body).to eq('Empty message body supplied with multipart/form-data; boundary=foobar content-type')
435
+ end
423
436
  end
424
437
 
425
438
  it 'responds with a 415 for an unsupported content-type' do
@@ -116,7 +116,7 @@ describe Grape::Entity do
116
116
  expect(last_response.body).to eq('Auto-detect!')
117
117
  end
118
118
 
119
- it 'does not run autodetection for Entity when explicitely provided' do
119
+ it 'does not run autodetection for Entity when explicitly provided' do
120
120
  entity = Class.new(Grape::Entity)
121
121
  some_array = []
122
122
 
@@ -238,14 +238,14 @@ describe Grape::Entity do
238
238
  get '/example'
239
239
  expect(last_response.status).to eq(200)
240
240
  expect(last_response.headers['Content-type']).to eq('application/xml')
241
- expect(last_response.body).to eq <<-XML
242
- <?xml version="1.0" encoding="UTF-8"?>
243
- <hash>
244
- <example>
245
- <name>johnnyiller</name>
246
- </example>
247
- </hash>
248
- XML
241
+ expect(last_response.body).to eq <<~XML
242
+ <?xml version="1.0" encoding="UTF-8"?>
243
+ <hash>
244
+ <example>
245
+ <name>johnnyiller</name>
246
+ </example>
247
+ </hash>
248
+ XML
249
249
  end
250
250
 
251
251
  it 'presents with json' do
@@ -326,7 +326,7 @@ XML
326
326
  end
327
327
  get '/example'
328
328
  expect_response_json = {
329
- 'page' => 1,
329
+ 'page' => 1,
330
330
  'user1' => { 'name' => 'user1' },
331
331
  'user2' => { 'name' => 'user2' }
332
332
  }
@@ -5,7 +5,7 @@ require 'spec_helper'
5
5
  describe Grape::Middleware::Auth::DSL do
6
6
  subject { Class.new(Grape::API) }
7
7
 
8
- let(:block) { ->() {} }
8
+ let(:block) { -> {} }
9
9
  let(:settings) do
10
10
  {
11
11
  opaque: 'secret',
@@ -16,8 +16,7 @@ describe Grape::Middleware::Error do
16
16
 
17
17
  class ErrApp
18
18
  class << self
19
- attr_accessor :error
20
- attr_accessor :format
19
+ attr_accessor :error, :format
21
20
 
22
21
  def call(_env)
23
22
  throw :error, error
@@ -20,7 +20,7 @@ describe Grape::Middleware::Formatter do
20
20
  let(:body) { ['foo'] }
21
21
  it 'calls #to_json since default format is json' do
22
22
  body.instance_eval do
23
- def to_json
23
+ def to_json(*_args)
24
24
  '"bar"'
25
25
  end
26
26
  end
@@ -33,7 +33,7 @@ describe Grape::Middleware::Formatter do
33
33
  let(:body) { { 'foos' => [{ 'bar' => 'baz' }] } }
34
34
  it 'calls #to_json if the content type is jsonapi' do
35
35
  body.instance_eval do
36
- def to_json
36
+ def to_json(*_args)
37
37
  '{"foos":[{"bar":"baz"}] }'
38
38
  end
39
39
  end
@@ -5,7 +5,9 @@ require 'spec_helper'
5
5
  describe Grape::Middleware::Stack do
6
6
  module StackSpec
7
7
  class FooMiddleware; end
8
+
8
9
  class BarMiddleware; end
10
+
9
11
  class BlockMiddleware
10
12
  attr_reader :block
11
13
 
@@ -15,7 +17,7 @@ describe Grape::Middleware::Stack do
15
17
  end
16
18
  end
17
19
 
18
- let(:proc) { ->() {} }
20
+ let(:proc) { -> {} }
19
21
  let(:others) { [[:use, StackSpec::BarMiddleware], [:insert_before, StackSpec::BarMiddleware, StackSpec::BlockMiddleware, proc]] }
20
22
 
21
23
  subject { Grape::Middleware::Stack.new }
@@ -35,8 +37,7 @@ describe Grape::Middleware::Stack do
35
37
  expect { subject.use StackSpec::BarMiddleware, false, my_arg: 42 }
36
38
  .to change { subject.size }.by(1)
37
39
  expect(subject.last).to eq(StackSpec::BarMiddleware)
38
- expect(subject.last.args).to eq([false])
39
- expect(subject.last.opts).to eq(my_arg: 42)
40
+ expect(subject.last.args).to eq([false, { my_arg: 42 }])
40
41
  end
41
42
 
42
43
  it 'pushes a middleware class with block arguments onto the stack' do
@@ -75,7 +75,7 @@ module Grape
75
75
  Grape.config.reset
76
76
  end
77
77
 
78
- subject(:request_params) { Grape::Request.new(env, opts).params }
78
+ subject(:request_params) { Grape::Request.new(env, **opts).params }
79
79
 
80
80
  context 'when the API does not include a specific param builder' do
81
81
  let(:opts) { {} }
@@ -13,8 +13,8 @@ describe Grape::Validations::MultipleAttributesIterator do
13
13
  { first: 'string', second: 'string' }
14
14
  end
15
15
 
16
- it 'yields the whole params hash without the list of attrs' do
17
- expect { |b| iterator.each(&b) }.to yield_with_args(params)
16
+ it 'yields the whole params hash and the skipped flag without the list of attrs' do
17
+ expect { |b| iterator.each(&b) }.to yield_with_args(params, false)
18
18
  end
19
19
  end
20
20
 
@@ -24,7 +24,17 @@ describe Grape::Validations::MultipleAttributesIterator do
24
24
  end
25
25
 
26
26
  it 'yields each element of the array without the list of attrs' do
27
- expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1])
27
+ expect { |b| iterator.each(&b) }.to yield_successive_args([params[0], false], [params[1], false])
28
+ end
29
+ end
30
+
31
+ context 'when params is empty optional placeholder' do
32
+ let(:params) do
33
+ [Grape::DSL::Parameters::EmptyOptionalValue, { first: 'string2', second: 'string2' }]
34
+ end
35
+
36
+ it 'yields each element of the array without the list of attrs' do
37
+ expect { |b| iterator.each(&b) }.to yield_successive_args([Grape::DSL::Parameters::EmptyOptionalValue, true], [params[1], false])
28
38
  end
29
39
  end
30
40
  end
@@ -98,6 +98,7 @@ describe Grape::Validations::ParamsScope do
98
98
 
99
99
  def self.parse(value)
100
100
  raise if value == 'invalid'
101
+
101
102
  new(value)
102
103
  end
103
104
 
@@ -144,7 +145,7 @@ describe Grape::Validations::ParamsScope do
144
145
  get '/renaming-coerced', foo: ' there we go '
145
146
 
146
147
  expect(last_response.status).to eq(200)
147
- expect(last_response.body).to eq('there we go-')
148
+ expect(last_response.body).to eq('-there we go')
148
149
  end
149
150
 
150
151
  it do
@@ -180,6 +181,28 @@ describe Grape::Validations::ParamsScope do
180
181
  expect(last_response.status).to eq(200)
181
182
  expect(last_response.body).to eq('{"baz":{"qux":"any"}}')
182
183
  end
184
+
185
+ it 'renaming can be defined before default' do
186
+ subject.params do
187
+ optional :foo, as: :bar, default: 'before'
188
+ end
189
+ subject.get('/rename-before-default') { declared(params)[:bar] }
190
+ get '/rename-before-default'
191
+
192
+ expect(last_response.status).to eq(200)
193
+ expect(last_response.body).to eq('before')
194
+ end
195
+
196
+ it 'renaming can be defined after default' do
197
+ subject.params do
198
+ optional :foo, default: 'after', as: :bar
199
+ end
200
+ subject.get('/rename-after-default') { declared(params)[:bar] }
201
+ get '/rename-after-default'
202
+
203
+ expect(last_response.status).to eq(200)
204
+ expect(last_response.body).to eq('after')
205
+ end
183
206
  end
184
207
 
185
208
  context 'array without coerce type explicitly given' do
@@ -568,7 +591,7 @@ describe Grape::Validations::ParamsScope do
568
591
  it 'allows renaming of dependent on parameter' do
569
592
  subject.params do
570
593
  optional :a, as: :b
571
- given b: ->(val) { val == 'x' } do
594
+ given a: ->(val) { val == 'x' } do
572
595
  requires :c
573
596
  end
574
597
  end
@@ -582,7 +605,7 @@ describe Grape::Validations::ParamsScope do
582
605
  expect(last_response.status).to eq 200
583
606
  end
584
607
 
585
- it 'raises an error if the dependent parameter is not the renamed one' do
608
+ it 'does not raise if the dependent parameter is not the renamed one' do
586
609
  expect do
587
610
  subject.params do
588
611
  optional :a, as: :b
@@ -590,6 +613,17 @@ describe Grape::Validations::ParamsScope do
590
613
  requires :c
591
614
  end
592
615
  end
616
+ end.not_to raise_error
617
+ end
618
+
619
+ it 'raises an error if the dependent parameter is the renamed one' do
620
+ expect do
621
+ subject.params do
622
+ optional :a, as: :b
623
+ given :b do
624
+ requires :c
625
+ end
626
+ end
593
627
  end.to raise_error(Grape::Exceptions::UnknownParameter)
594
628
  end
595
629
 
@@ -15,7 +15,7 @@ describe Grape::Validations::SingleAttributeIterator do
15
15
 
16
16
  it 'yields params and every single attribute from the list' do
17
17
  expect { |b| iterator.each(&b) }
18
- .to yield_successive_args([params, :first, false], [params, :second, false])
18
+ .to yield_successive_args([params, :first, false, false], [params, :second, false, false])
19
19
  end
20
20
  end
21
21
 
@@ -26,8 +26,8 @@ describe Grape::Validations::SingleAttributeIterator do
26
26
 
27
27
  it 'yields every single attribute from the list for each of the array elements' do
28
28
  expect { |b| iterator.each(&b) }.to yield_successive_args(
29
- [params[0], :first, false], [params[0], :second, false],
30
- [params[1], :first, false], [params[1], :second, false]
29
+ [params[0], :first, false, false], [params[0], :second, false, false],
30
+ [params[1], :first, false, false], [params[1], :second, false, false]
31
31
  )
32
32
  end
33
33
 
@@ -36,9 +36,20 @@ describe Grape::Validations::SingleAttributeIterator do
36
36
 
37
37
  it 'marks params with empty values' do
38
38
  expect { |b| iterator.each(&b) }.to yield_successive_args(
39
- [params[0], :first, true], [params[0], :second, true],
40
- [params[1], :first, true], [params[1], :second, true],
41
- [params[2], :first, false], [params[2], :second, false]
39
+ [params[0], :first, true, false], [params[0], :second, true, false],
40
+ [params[1], :first, true, false], [params[1], :second, true, false],
41
+ [params[2], :first, false, false], [params[2], :second, false, false]
42
+ )
43
+ end
44
+ end
45
+
46
+ context 'when missing optional value' do
47
+ let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue, 10] }
48
+
49
+ it 'marks params with skipped values' do
50
+ expect { |b| iterator.each(&b) }.to yield_successive_args(
51
+ [params[0], :first, false, true], [params[0], :second, false, true],
52
+ [params[1], :first, false, false], [params[1], :second, false, false]
42
53
  )
43
54
  end
44
55
  end
@@ -12,7 +12,7 @@ describe Grape::Validations::Types::PrimitiveCoercer do
12
12
  let(:type) { BigDecimal }
13
13
 
14
14
  it 'coerces to BigDecimal' do
15
- expect(subject.call(5)).to eq(BigDecimal(5))
15
+ expect(subject.call(5)).to eq(BigDecimal('5'))
16
16
  end
17
17
 
18
18
  it 'coerces an empty string to nil' do
@@ -127,7 +127,7 @@ describe Grape::Validations::Types::PrimitiveCoercer do
127
127
  end
128
128
 
129
129
  it 'returns a value as it is when the given value is BigDecimal' do
130
- expect(subject.call(BigDecimal(0))).to eq(BigDecimal(0))
130
+ expect(subject.call(BigDecimal('0'))).to eq(BigDecimal('0'))
131
131
  end
132
132
  end
133
133
  end