grape 1.3.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -1
  3. data/LICENSE +1 -1
  4. data/README.md +104 -21
  5. data/UPGRADING.md +265 -39
  6. data/lib/grape.rb +2 -2
  7. data/lib/grape/api.rb +2 -2
  8. data/lib/grape/api/instance.rb +29 -28
  9. data/lib/grape/dsl/helpers.rb +1 -0
  10. data/lib/grape/dsl/inside_route.rb +69 -36
  11. data/lib/grape/dsl/parameters.rb +7 -3
  12. data/lib/grape/dsl/routing.rb +2 -4
  13. data/lib/grape/dsl/validations.rb +18 -1
  14. data/lib/grape/eager_load.rb +1 -1
  15. data/lib/grape/endpoint.rb +8 -6
  16. data/lib/grape/http/headers.rb +1 -0
  17. data/lib/grape/middleware/base.rb +2 -1
  18. data/lib/grape/middleware/error.rb +10 -12
  19. data/lib/grape/middleware/formatter.rb +3 -3
  20. data/lib/grape/middleware/stack.rb +21 -8
  21. data/lib/grape/middleware/versioner/header.rb +1 -1
  22. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  23. data/lib/grape/path.rb +2 -2
  24. data/lib/grape/request.rb +1 -1
  25. data/lib/grape/router.rb +30 -43
  26. data/lib/grape/router/attribute_translator.rb +26 -5
  27. data/lib/grape/router/route.rb +3 -22
  28. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  29. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  30. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  31. data/lib/grape/util/base_inheritable.rb +11 -8
  32. data/lib/grape/util/lazy_value.rb +1 -0
  33. data/lib/grape/util/reverse_stackable_values.rb +3 -1
  34. data/lib/grape/util/stackable_values.rb +3 -1
  35. data/lib/grape/validations/attributes_iterator.rb +8 -0
  36. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  37. data/lib/grape/validations/params_scope.rb +8 -6
  38. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  39. data/lib/grape/validations/types.rb +6 -5
  40. data/lib/grape/validations/types/array_coercer.rb +14 -5
  41. data/lib/grape/validations/types/build_coercer.rb +5 -8
  42. data/lib/grape/validations/types/custom_type_coercer.rb +14 -2
  43. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  44. data/lib/grape/validations/types/file.rb +15 -13
  45. data/lib/grape/validations/types/json.rb +40 -36
  46. data/lib/grape/validations/types/primitive_coercer.rb +11 -5
  47. data/lib/grape/validations/types/set_coercer.rb +6 -4
  48. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  49. data/lib/grape/validations/validator_factory.rb +1 -1
  50. data/lib/grape/validations/validators/as.rb +1 -1
  51. data/lib/grape/validations/validators/base.rb +4 -5
  52. data/lib/grape/validations/validators/coerce.rb +3 -10
  53. data/lib/grape/validations/validators/default.rb +3 -5
  54. data/lib/grape/validations/validators/except_values.rb +1 -1
  55. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  56. data/lib/grape/validations/validators/regexp.rb +1 -1
  57. data/lib/grape/validations/validators/values.rb +1 -1
  58. data/lib/grape/version.rb +1 -1
  59. data/spec/grape/api/instance_spec.rb +50 -0
  60. data/spec/grape/api_spec.rb +75 -0
  61. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  62. data/spec/grape/endpoint/declared_spec.rb +601 -0
  63. data/spec/grape/endpoint_spec.rb +0 -521
  64. data/spec/grape/entity_spec.rb +6 -0
  65. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  66. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  67. data/spec/grape/middleware/error_spec.rb +1 -1
  68. data/spec/grape/middleware/formatter_spec.rb +1 -1
  69. data/spec/grape/middleware/stack_spec.rb +3 -1
  70. data/spec/grape/path_spec.rb +4 -4
  71. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  72. data/spec/grape/validations/params_scope_spec.rb +26 -0
  73. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  74. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  75. data/spec/grape/validations/types/primitive_coercer_spec.rb +65 -5
  76. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  77. data/spec/grape/validations/types_spec.rb +1 -1
  78. data/spec/grape/validations/validators/coerce_spec.rb +317 -29
  79. data/spec/grape/validations/validators/default_spec.rb +170 -0
  80. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  81. data/spec/grape/validations/validators/values_spec.rb +1 -1
  82. data/spec/grape/validations_spec.rb +290 -18
  83. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  84. data/spec/spec_helper.rb +0 -10
  85. data/spec/support/chunks.rb +14 -0
  86. data/spec/support/versioned_helpers.rb +3 -5
  87. metadata +18 -8
@@ -181,6 +181,7 @@ describe Grape::Entity do
181
181
  subject.get '/example' do
182
182
  c = Class.new do
183
183
  attr_reader :id
184
+
184
185
  def initialize(id)
185
186
  @id = id
186
187
  end
@@ -202,6 +203,7 @@ describe Grape::Entity do
202
203
  subject.get '/examples' do
203
204
  c = Class.new do
204
205
  attr_reader :id
206
+
205
207
  def initialize(id)
206
208
  @id = id
207
209
  end
@@ -226,6 +228,7 @@ describe Grape::Entity do
226
228
  subject.get '/example' do
227
229
  c = Class.new do
228
230
  attr_reader :name
231
+
229
232
  def initialize(args)
230
233
  @name = args[:name] || 'no name set'
231
234
  end
@@ -255,6 +258,7 @@ XML
255
258
  subject.get '/example' do
256
259
  c = Class.new do
257
260
  attr_reader :name
261
+
258
262
  def initialize(args)
259
263
  @name = args[:name] || 'no name set'
260
264
  end
@@ -284,6 +288,7 @@ XML
284
288
  subject.get '/example' do
285
289
  c = Class.new do
286
290
  attr_reader :name
291
+
287
292
  def initialize(args)
288
293
  @name = args[:name] || 'no name set'
289
294
  end
@@ -302,6 +307,7 @@ XML
302
307
  it 'present with multiple entities using optional symbol' do
303
308
  user = Class.new do
304
309
  attr_reader :name
310
+
305
311
  def initialize(args)
306
312
  @name = args[:name] || 'no name set'
307
313
  end
@@ -4,12 +4,16 @@ require 'spec_helper'
4
4
 
5
5
  describe Rack::Sendfile do
6
6
  subject do
7
- send_file = file_streamer
7
+ content_object = file_object
8
8
  app = Class.new(Grape::API) do
9
9
  use Rack::Sendfile
10
10
  format :json
11
11
  get do
12
- file send_file
12
+ if content_object.is_a?(String)
13
+ sendfile content_object
14
+ else
15
+ stream content_object
16
+ end
13
17
  end
14
18
  end
15
19
 
@@ -22,9 +26,9 @@ describe Rack::Sendfile do
22
26
  app.call(env)
23
27
  end
24
28
 
25
- context do
26
- let(:file_streamer) do
27
- double(:file_streamer, to_path: '/accel/mapping/some/path')
29
+ context 'when calling sendfile' do
30
+ let(:file_object) do
31
+ '/accel/mapping/some/path'
28
32
  end
29
33
 
30
34
  it 'contains Sendfile headers' do
@@ -33,9 +37,9 @@ describe Rack::Sendfile do
33
37
  end
34
38
  end
35
39
 
36
- context do
37
- let(:file_streamer) do
38
- double(:file_streamer)
40
+ context 'when streaming non file content' do
41
+ let(:file_object) do
42
+ double(:file_object, each: nil)
39
43
  end
40
44
 
41
45
  it 'not contains Sendfile headers' do
@@ -36,7 +36,7 @@ describe Grape::Middleware::Auth::Strategies do
36
36
  RSpec::Matchers.define :be_challenge do
37
37
  match do |actual_response|
38
38
  actual_response.status == 401 &&
39
- actual_response['WWW-Authenticate'] =~ /^Digest / &&
39
+ actual_response['WWW-Authenticate'].start_with?('Digest ') &&
40
40
  actual_response.body.empty?
41
41
  end
42
42
  end
@@ -30,7 +30,7 @@ describe Grape::Middleware::Error do
30
30
  opts = options
31
31
  Rack::Builder.app do
32
32
  use Spec::Support::EndpointFaker
33
- use Grape::Middleware::Error, opts
33
+ use Grape::Middleware::Error, **opts
34
34
  run ErrorSpec::ErrApp
35
35
  end
36
36
  end
@@ -380,7 +380,7 @@ describe Grape::Middleware::Formatter do
380
380
 
381
381
  context 'send file' do
382
382
  let(:file) { double(File) }
383
- let(:file_body) { Grape::ServeFile::FileResponse.new(file) }
383
+ let(:file_body) { Grape::ServeStream::StreamResponse.new(file) }
384
384
  let(:app) { ->(_env) { [200, {}, file_body] } }
385
385
 
386
386
  it 'returns a file response' do
@@ -8,6 +8,7 @@ describe Grape::Middleware::Stack do
8
8
  class BarMiddleware; end
9
9
  class BlockMiddleware
10
10
  attr_reader :block
11
+
11
12
  def initialize(&block)
12
13
  @block = block
13
14
  end
@@ -34,7 +35,8 @@ describe Grape::Middleware::Stack do
34
35
  expect { subject.use StackSpec::BarMiddleware, false, my_arg: 42 }
35
36
  .to change { subject.size }.by(1)
36
37
  expect(subject.last).to eq(StackSpec::BarMiddleware)
37
- expect(subject.last.args).to eq([false, { my_arg: 42 }])
38
+ expect(subject.last.args).to eq([false])
39
+ expect(subject.last.opts).to eq(my_arg: 42)
38
40
  end
39
41
 
40
42
  it 'pushes a middleware class with block arguments onto the stack' do
@@ -87,12 +87,12 @@ module Grape
87
87
  describe '#namespace?' do
88
88
  it 'is false when the namespace is nil' do
89
89
  path = Path.new(anything, nil, anything)
90
- expect(path.namespace?).to be nil
90
+ expect(path.namespace?).to be_falsey
91
91
  end
92
92
 
93
93
  it 'is false when the namespace starts with whitespace' do
94
94
  path = Path.new(anything, ' /foo', anything)
95
- expect(path.namespace?).to be nil
95
+ expect(path.namespace?).to be_falsey
96
96
  end
97
97
 
98
98
  it 'is false when the namespace is the root path' do
@@ -109,12 +109,12 @@ module Grape
109
109
  describe '#path?' do
110
110
  it 'is false when the path is nil' do
111
111
  path = Path.new(nil, anything, anything)
112
- expect(path.path?).to be nil
112
+ expect(path.path?).to be_falsey
113
113
  end
114
114
 
115
115
  it 'is false when the path starts with whitespace' do
116
116
  path = Path.new(' /foo', anything, anything)
117
- expect(path.path?).to be nil
117
+ expect(path.path?).to be_falsey
118
118
  end
119
119
 
120
120
  it 'is false when the path is the root path' do
@@ -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
@@ -633,6 +633,32 @@ describe Grape::Validations::ParamsScope do
633
633
  expect(last_response.status).to eq(200)
634
634
  end
635
635
 
636
+ it 'detect unmet nested dependency' do
637
+ subject.params do
638
+ requires :a, type: String, allow_blank: false, values: %w[x y z]
639
+ given a: ->(val) { val == 'z' } do
640
+ requires :inner3, type: Array, allow_blank: false do
641
+ requires :bar, type: String, allow_blank: false
642
+ given bar: ->(val) { val == 'b' } do
643
+ requires :baz, type: Array do
644
+ optional :baz_category, type: String
645
+ end
646
+ end
647
+ given bar: ->(val) { val == 'c' } do
648
+ requires :baz, type: Array do
649
+ requires :baz_category, type: String
650
+ end
651
+ end
652
+ end
653
+ end
654
+ end
655
+ subject.get('/nested-dependency') { declared(params).to_json }
656
+
657
+ get '/nested-dependency', a: 'z', inner3: [{ bar: 'c', baz: [{ unrelated: 'nope' }] }]
658
+ expect(last_response.status).to eq(400)
659
+ expect(last_response.body).to eq 'inner3[0][baz][0][baz_category] is missing'
660
+ end
661
+
636
662
  it 'includes the parameter within #declared(params)' do
637
663
  get '/test', a: true, b: true
638
664
 
@@ -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
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Grape::Validations::Types::ArrayCoercer do
6
+ subject { described_class.new(type) }
7
+
8
+ describe '#call' do
9
+ context 'an array of primitives' do
10
+ let(:type) { Array[String] }
11
+
12
+ it 'coerces elements in the array' do
13
+ expect(subject.call([10, 20])).to eq(%w[10 20])
14
+ end
15
+ end
16
+
17
+ context 'an array of arrays' do
18
+ let(:type) { Array[Array[Integer]] }
19
+
20
+ it 'coerces elements in the nested array' do
21
+ expect(subject.call([%w[10 20]])).to eq([[10, 20]])
22
+ expect(subject.call([['10'], ['20']])).to eq([[10], [20]])
23
+ end
24
+ end
25
+
26
+ context 'an array of sets' do
27
+ let(:type) { Array[Set[Integer]] }
28
+
29
+ it 'coerces elements in the nested set' do
30
+ expect(subject.call([%w[10 20]])).to eq([Set[10, 20]])
31
+ expect(subject.call([['10'], ['20']])).to eq([Set[10], Set[20]])
32
+ end
33
+ end
34
+ end
35
+ end
@@ -7,7 +7,19 @@ describe Grape::Validations::Types::PrimitiveCoercer do
7
7
 
8
8
  subject { described_class.new(type, strict) }
9
9
 
10
- describe '.call' do
10
+ describe '#call' do
11
+ context 'BigDecimal' do
12
+ let(:type) { BigDecimal }
13
+
14
+ it 'coerces to BigDecimal' do
15
+ expect(subject.call(5)).to eq(BigDecimal(5))
16
+ end
17
+
18
+ it 'coerces an empty string to nil' do
19
+ expect(subject.call('')).to be_nil
20
+ end
21
+ end
22
+
11
23
  context 'Boolean' do
12
24
  let(:type) { Grape::API::Boolean }
13
25
 
@@ -26,6 +38,50 @@ describe Grape::Validations::Types::PrimitiveCoercer do
26
38
  it 'returns an error when the given value cannot be coerced' do
27
39
  expect(subject.call(123)).to be_instance_of(Grape::Validations::Types::InvalidValue)
28
40
  end
41
+
42
+ it 'coerces an empty string to nil' do
43
+ expect(subject.call('')).to be_nil
44
+ end
45
+ end
46
+
47
+ context 'DateTime' do
48
+ let(:type) { DateTime }
49
+
50
+ it 'coerces an empty string to nil' do
51
+ expect(subject.call('')).to be_nil
52
+ end
53
+ end
54
+
55
+ context 'Float' do
56
+ let(:type) { Float }
57
+
58
+ it 'coerces an empty string to nil' do
59
+ expect(subject.call('')).to be_nil
60
+ end
61
+ end
62
+
63
+ context 'Integer' do
64
+ let(:type) { Integer }
65
+
66
+ it 'coerces an empty string to nil' do
67
+ expect(subject.call('')).to be_nil
68
+ end
69
+ end
70
+
71
+ context 'Numeric' do
72
+ let(:type) { Numeric }
73
+
74
+ it 'coerces an empty string to nil' do
75
+ expect(subject.call('')).to be_nil
76
+ end
77
+ end
78
+
79
+ context 'Time' do
80
+ let(:type) { Time }
81
+
82
+ it 'coerces an empty string to nil' do
83
+ expect(subject.call('')).to be_nil
84
+ end
29
85
  end
30
86
 
31
87
  context 'String' do
@@ -34,13 +90,17 @@ describe Grape::Validations::Types::PrimitiveCoercer do
34
90
  it 'coerces to String' do
35
91
  expect(subject.call(10)).to eq('10')
36
92
  end
93
+
94
+ it 'does not coerce an empty string to nil' do
95
+ expect(subject.call('')).to eq('')
96
+ end
37
97
  end
38
98
 
39
- context 'BigDecimal' do
40
- let(:type) { BigDecimal }
99
+ context 'Symbol' do
100
+ let(:type) { Symbol }
41
101
 
42
- it 'coerces to BigDecimal' do
43
- expect(subject.call(5)).to eq(BigDecimal(5))
102
+ it 'coerces an empty string to nil' do
103
+ expect(subject.call('')).to be_nil
44
104
  end
45
105
  end
46
106
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Grape::Validations::Types::SetCoercer do
6
+ subject { described_class.new(type) }
7
+
8
+ describe '#call' do
9
+ context 'a set of primitives' do
10
+ let(:type) { Set[String] }
11
+
12
+ it 'coerces elements to the set' do
13
+ expect(subject.call([10, 20])).to eq(Set['10', '20'])
14
+ end
15
+ end
16
+
17
+ context 'a set of sets' do
18
+ let(:type) { Set[Set[Integer]] }
19
+
20
+ it 'coerces elements in the nested set' do
21
+ expect(subject.call([%w[10 20]])).to eq(Set[Set[10, 20]])
22
+ expect(subject.call([['10'], ['20']])).to eq(Set[Set[10], Set[20]])
23
+ end
24
+ end
25
+
26
+ context 'a set of sets of arrays' do
27
+ let(:type) { Set[Set[Array[Integer]]] }
28
+
29
+ it 'coerces elements in the nested set' do
30
+ expect(subject.call([[['10'], ['20']]])).to eq(Set[Set[Array[10], Array[20]]])
31
+ end
32
+ end
33
+ end
34
+ end
@@ -17,7 +17,7 @@ describe Grape::Validations::Types do
17
17
  [
18
18
  Integer, Float, Numeric, BigDecimal,
19
19
  Grape::API::Boolean, String, Symbol,
20
- Date, DateTime, Time, Rack::Multipart::UploadedFile
20
+ Date, DateTime, Time
21
21
  ].each do |type|
22
22
  it "recognizes #{type} as a primitive" do
23
23
  expect(described_class.primitive?(type)).to be_truthy