grape 4.0.0 → 4.1.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -1
  3. data/README.md +195 -95
  4. data/UPGRADING.md +364 -0
  5. data/grape.gemspec +1 -1
  6. data/lib/grape/api/instance.rb +57 -30
  7. data/lib/grape/api.rb +52 -18
  8. data/lib/grape/content_types.rb +26 -9
  9. data/lib/grape/cookies.rb +1 -1
  10. data/lib/grape/declared_params_handler.rb +35 -7
  11. data/lib/grape/dsl/declared.rb +5 -4
  12. data/lib/grape/dsl/desc.rb +1 -2
  13. data/lib/grape/dsl/entity.rb +35 -19
  14. data/lib/grape/dsl/headers.rb +3 -3
  15. data/lib/grape/dsl/helpers.rb +5 -8
  16. data/lib/grape/dsl/inside_route.rb +33 -27
  17. data/lib/grape/dsl/logger.rb +10 -4
  18. data/lib/grape/dsl/middleware.rb +4 -9
  19. data/lib/grape/dsl/mounting.rb +149 -0
  20. data/lib/grape/dsl/parameters.rb +78 -104
  21. data/lib/grape/dsl/request_response.rb +35 -22
  22. data/lib/grape/dsl/routing.rb +17 -191
  23. data/lib/grape/dsl/settings.rb +1 -1
  24. data/lib/grape/dsl/version_options.rb +2 -2
  25. data/lib/grape/dsl/versioning.rb +98 -0
  26. data/lib/grape/endpoint/state.rb +15 -0
  27. data/lib/grape/endpoint.rb +229 -137
  28. data/lib/grape/error_formatter/base.rb +28 -13
  29. data/lib/grape/error_formatter/json.rb +3 -0
  30. data/lib/grape/error_formatter/jsonl.rb +15 -0
  31. data/lib/grape/error_formatter/xml.rb +3 -1
  32. data/lib/grape/exceptions/error_response.rb +23 -7
  33. data/lib/grape/exceptions/halt.rb +39 -0
  34. data/lib/grape/exceptions/validation.rb +9 -9
  35. data/lib/grape/exceptions/validation_errors.rb +12 -2
  36. data/lib/grape/formatter/jsonl.rb +34 -0
  37. data/lib/grape/formatter/markdown.rb +17 -0
  38. data/lib/grape/formatter/md.rb +11 -0
  39. data/lib/grape/locale/en.yml +1 -0
  40. data/lib/grape/middleware/auth/base.rb +16 -1
  41. data/lib/grape/middleware/auth/strategy_info.rb +2 -2
  42. data/lib/grape/middleware/base.rb +19 -13
  43. data/lib/grape/middleware/error.rb +133 -35
  44. data/lib/grape/middleware/formatter.rb +235 -94
  45. data/lib/grape/middleware/precomputed_content_types.rb +14 -2
  46. data/lib/grape/middleware/stack.rb +7 -8
  47. data/lib/grape/middleware/versioner/base.rb +26 -10
  48. data/lib/grape/middleware/versioner/header.rb +70 -15
  49. data/lib/grape/middleware/versioner/param.rb +3 -5
  50. data/lib/grape/middleware/versioner/path.rb +20 -11
  51. data/lib/grape/middleware/versioner.rb +1 -1
  52. data/lib/grape/mountable.rb +2 -4
  53. data/lib/grape/namespace.rb +2 -3
  54. data/lib/grape/parser/jsonl.rb +19 -0
  55. data/lib/grape/request.rb +46 -8
  56. data/lib/grape/router/base_route.rb +41 -0
  57. data/lib/grape/router/greedy_route.rb +1 -1
  58. data/lib/grape/router/mustermann_pattern.rb +1 -1
  59. data/lib/grape/router/pattern/path.rb +1 -1
  60. data/lib/grape/router/pattern.rb +11 -5
  61. data/lib/grape/router/route.rb +42 -11
  62. data/lib/grape/router/route_buckets.rb +246 -0
  63. data/lib/grape/router.rb +262 -32
  64. data/lib/grape/util/endpoint_configuration.rb +7 -1
  65. data/lib/grape/util/inheritable_setting.rb +80 -161
  66. data/lib/grape/util/lazy/value.rb +52 -11
  67. data/lib/grape/util/media_type.rb +38 -15
  68. data/lib/grape/util/path_normalizer.rb +7 -2
  69. data/lib/grape/util/registry.rb +9 -2
  70. data/lib/grape/util/scrub.rb +33 -0
  71. data/lib/grape/util/translation.rb +6 -6
  72. data/lib/grape/validations/attributes_iterator.rb +73 -37
  73. data/lib/grape/validations/coerce_options.rb +1 -1
  74. data/lib/grape/validations/params_documentation.rb +15 -7
  75. data/lib/grape/validations/params_scope.rb +176 -43
  76. data/lib/grape/validations/types/custom_type_coercer.rb +2 -3
  77. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  78. data/lib/grape/validations/types/dry_type_coercer.rb +20 -12
  79. data/lib/grape/validations/types/multiple_type_coercer.rb +1 -2
  80. data/lib/grape/validations/types/primitive_coercer.rb +17 -0
  81. data/lib/grape/validations/types/variant_collection_coercer.rb +24 -10
  82. data/lib/grape/validations/types.rb +12 -18
  83. data/lib/grape/validations/validations_spec.rb +53 -22
  84. data/lib/grape/validations/validators/allow_blank_validator.rb +2 -2
  85. data/lib/grape/validations/validators/as_validator.rb +2 -3
  86. data/lib/grape/validations/validators/base.rb +124 -25
  87. data/lib/grape/validations/validators/coerce_validator.rb +2 -3
  88. data/lib/grape/validations/validators/default_validator.rb +18 -2
  89. data/lib/grape/validations/validators/except_values_validator.rb +16 -3
  90. data/lib/grape/validations/validators/hash_elements_validator.rb +34 -0
  91. data/lib/grape/validations/validators/length_validator.rb +24 -16
  92. data/lib/grape/validations/validators/multiple_params_base.rb +0 -4
  93. data/lib/grape/validations/validators/values_validator.rb +18 -24
  94. data/lib/grape/version.rb +1 -2
  95. data/lib/grape.rb +3 -0
  96. metadata +18 -11
  97. data/lib/grape/util/lazy/value_array.rb +0 -21
  98. data/lib/grape/util/lazy/value_enumerable.rb +0 -31
  99. data/lib/grape/util/lazy/value_hash.rb +0 -21
  100. data/lib/grape/validations/multiple_attributes_iterator.rb +0 -13
  101. data/lib/grape/validations/single_attribute_iterator.rb +0 -28
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ # Rejects an element that is not a Hash in an Array param given a block,
7
+ # which ParamsScope registers this for: the block declares the keys of
8
+ # each element, and an element with no keys cannot hold any of them.
9
+ # Without it, an element like +"x"+ or a nested Array only failed where
10
+ # the block required a key of it, so a block of optional params let
11
+ # anything through.
12
+ #
13
+ # An Array whose elements are all blank is passed over, as the block's
14
+ # own validators pass over an optional scope whose elements are all
15
+ # blank. Otherwise a blank element is not a Hash either. Elements are
16
+ # scrubbed first, since String#blank? raises on bytes their encoding
17
+ # does not allow.
18
+ class HashElementsValidator < Base
19
+ default_message_key :coerce
20
+
21
+ # Registered not required, so it is only asked about params that are a
22
+ # Hash (see Base#validate!).
23
+ def validate_param!(attr_name, params)
24
+ elements = params[attr_name]
25
+ return unless elements.is_a?(Array)
26
+ return if elements.all? { |element| Grape::Util::Scrub.blank?(element) }
27
+
28
+ index = elements.index { |element| !hash_like?(element) }
29
+ validation_error!(["#{scope.full_name(attr_name)}[#{index}]"]) if index
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -12,9 +12,7 @@ module Grape
12
12
  validate_boundary!(:max, @max)
13
13
  raise ArgumentError, "min `#{@min}` cannot be greater than max `#{@max}`" if @min && @max && @min > @max
14
14
 
15
- return if @is.nil?
16
- raise ArgumentError, 'is must be an integer greater than zero' unless @is.is_a?(Integer) && @is.positive?
17
- raise ArgumentError, 'is cannot be combined with min or max' unless @min.nil? && @max.nil?
15
+ validate_is! unless @is.nil?
18
16
  end
19
17
 
20
18
  def validate_param!(attr_name, params)
@@ -23,24 +21,34 @@ module Grape
23
21
  param = params[attr_name]
24
22
 
25
23
  return unless param.respond_to?(:length)
24
+ return if within_bounds?(param.length)
26
25
 
27
- return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max) || (!@is.nil? && param.length != @is)
28
-
29
- validation_error!(attr_name, message do
30
- if @min && @max
31
- translate(:length, min: @min, max: @max)
32
- elsif @min
33
- translate(:length_min, min: @min)
34
- elsif @max
35
- translate(:length_max, max: @max)
36
- else
37
- translate(:length_is, is: @is)
38
- end
39
- end)
26
+ validation_error!(attr_name, message { length_message })
40
27
  end
41
28
 
42
29
  private
43
30
 
31
+ def validate_is!
32
+ raise ArgumentError, 'is must be an integer greater than zero' unless @is.is_a?(Integer) && @is.positive?
33
+ raise ArgumentError, 'is cannot be combined with min or max' unless @min.nil? && @max.nil?
34
+ end
35
+
36
+ def within_bounds?(length)
37
+ (@min.nil? || length >= @min) && (@max.nil? || length <= @max) && (@is.nil? || length == @is)
38
+ end
39
+
40
+ def length_message
41
+ if @min && @max
42
+ translate(:length, min: @min, max: @max)
43
+ elsif @min
44
+ translate(:length_min, min: @min)
45
+ elsif @max
46
+ translate(:length_max, max: @max)
47
+ else
48
+ translate(:length_is, is: @is)
49
+ end
50
+ end
51
+
44
52
  def validate_boundary!(name, val)
45
53
  raise ArgumentError, "`#{name}` must be an integer greater than or equal to zero" if !val.nil? && (!val.is_a?(Integer) || val.negative?)
46
54
  end
@@ -18,10 +18,6 @@ module Grape
18
18
 
19
19
  private
20
20
 
21
- def iterator_class
22
- MultipleAttributesIterator
23
- end
24
-
25
21
  def keys_in_common(resource_params, known_keys = all_keys)
26
22
  return [] unless hash_like?(resource_params)
27
23
 
@@ -12,12 +12,13 @@ module Grape
12
12
 
13
13
  # Zero-arity procs return a collection per-request (e.g. DB-backed lists).
14
14
  # Non-zero-arity procs are per-element predicates, called directly at validation time.
15
- # Non-Proc values are wrapped in a zero-arity lambda for a uniform call interface.
15
+ # Anything else is the collection itself, held as it came rather than
16
+ # behind a lambda that would only hand it back.
16
17
  if values.is_a?(Proc)
17
18
  @values_call = values
18
19
  @values_is_predicate = !values.arity.zero?
19
20
  else
20
- @values_call = -> { values }
21
+ @values = values
21
22
  @values_is_predicate = false
22
23
  end
23
24
  end
@@ -27,8 +28,8 @@ module Grape
27
28
 
28
29
  val = scrub(params[attr_name])
29
30
 
30
- return if val.nil? && !required_for_root_scope?
31
- return if val != false && val.blank? && allow_blank?
31
+ return if val.nil? && !required?
32
+ return if blank_and_allowed?(val)
32
33
  return if check_values?(val, attr_name)
33
34
 
34
35
  validation_error!(attr_name)
@@ -36,31 +37,24 @@ module Grape
36
37
 
37
38
  private
38
39
 
40
+ # +false+ is a value: AS's +blank?+ says otherwise.
41
+ def blank_and_allowed?(val)
42
+ val != false && val.blank? && allow_blank?
43
+ end
44
+
39
45
  def check_values?(val, attr_name)
40
46
  param_array = val.nil? ? [nil] : Array.wrap(val)
47
+ return predicate_holds?(param_array, attr_name) if @values_is_predicate
41
48
 
42
- if @values_is_predicate
43
- begin
44
- param_array.all? { |param| @values_call.call(param) }
45
- rescue StandardError => e
46
- warn "Error '#{e}' raised while validating attribute '#{attr_name}'"
47
- false
48
- end
49
- else
50
- values = @values_call.call
51
- return true if values.nil?
52
-
53
- param_array.all? { |param| values.include?(param) }
54
- end
49
+ values = @values_call ? @values_call.call : @values
50
+ values.nil? || param_array.all? { |param| values.include?(param) }
55
51
  end
56
52
 
57
- def required_for_root_scope?
58
- return false unless required?
59
-
60
- current_scope = scope
61
- current_scope = current_scope.parent while current_scope.lateral?
62
-
63
- current_scope.root?
53
+ def predicate_holds?(param_array, attr_name)
54
+ param_array.all? { |param| @values_call.call(param) }
55
+ rescue StandardError => e
56
+ warn "Error '#{e}' raised while validating attribute '#{attr_name}'"
57
+ false
64
58
  end
65
59
  end
66
60
  end
data/lib/grape/version.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grape
4
- # The current version of Grape.
5
- VERSION = '4.0.0'
4
+ VERSION = '4.1.0'
6
5
  end
data/lib/grape.rb CHANGED
@@ -61,6 +61,9 @@ module Grape
61
61
  # Let an error response that cannot be rendered propagate out of the
62
62
  # middleware stack instead of being answered with a failsafe 500.
63
63
  setting :raise_rendering_errors, default: false
64
+ # Let an exception raised inside a `rescue_from` block propagate out of the
65
+ # middleware stack instead of being redispatched.
66
+ setting :raise_rescue_handler_errors, default: false
64
67
 
65
68
  # The HTTP QUERY method (RFC 10008): a safe, idempotent request whose content
66
69
  # carries the query. Rack has no constant for it yet, hence the literal.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -123,6 +123,7 @@ files:
123
123
  - lib/grape/dsl/inside_route.rb
124
124
  - lib/grape/dsl/logger.rb
125
125
  - lib/grape/dsl/middleware.rb
126
+ - lib/grape/dsl/mounting.rb
126
127
  - lib/grape/dsl/parameters.rb
127
128
  - lib/grape/dsl/request_response.rb
128
129
  - lib/grape/dsl/rescue_options.rb
@@ -130,17 +131,21 @@ files:
130
131
  - lib/grape/dsl/settings.rb
131
132
  - lib/grape/dsl/validations.rb
132
133
  - lib/grape/dsl/version_options.rb
134
+ - lib/grape/dsl/versioning.rb
133
135
  - lib/grape/endpoint.rb
134
136
  - lib/grape/endpoint/options.rb
137
+ - lib/grape/endpoint/state.rb
135
138
  - lib/grape/env.rb
136
139
  - lib/grape/error_formatter.rb
137
140
  - lib/grape/error_formatter/base.rb
138
141
  - lib/grape/error_formatter/json.rb
142
+ - lib/grape/error_formatter/jsonl.rb
139
143
  - lib/grape/error_formatter/serializable_hash.rb
140
144
  - lib/grape/error_formatter/txt.rb
141
145
  - lib/grape/error_formatter/xml.rb
142
146
  - lib/grape/exceptions/base.rb
143
147
  - lib/grape/exceptions/error_response.rb
148
+ - lib/grape/exceptions/halt.rb
144
149
  - lib/grape/exceptions/incompatible_option_values.rb
145
150
  - lib/grape/exceptions/internal_server_error.rb
146
151
  - lib/grape/exceptions/invalid_accept_header.rb
@@ -167,6 +172,9 @@ files:
167
172
  - lib/grape/formatter.rb
168
173
  - lib/grape/formatter/base.rb
169
174
  - lib/grape/formatter/json.rb
175
+ - lib/grape/formatter/jsonl.rb
176
+ - lib/grape/formatter/markdown.rb
177
+ - lib/grape/formatter/md.rb
170
178
  - lib/grape/formatter/serializable_hash.rb
171
179
  - lib/grape/formatter/txt.rb
172
180
  - lib/grape/formatter/xml.rb
@@ -198,6 +206,7 @@ files:
198
206
  - lib/grape/parser.rb
199
207
  - lib/grape/parser/base.rb
200
208
  - lib/grape/parser/json.rb
209
+ - lib/grape/parser/jsonl.rb
201
210
  - lib/grape/parser/xml.rb
202
211
  - lib/grape/path.rb
203
212
  - lib/grape/precompiled_json.rb
@@ -211,6 +220,7 @@ files:
211
220
  - lib/grape/router/pattern.rb
212
221
  - lib/grape/router/pattern/path.rb
213
222
  - lib/grape/router/route.rb
223
+ - lib/grape/router/route_buckets.rb
214
224
  - lib/grape/serve_stream/file_body.rb
215
225
  - lib/grape/serve_stream/sendfile_response.rb
216
226
  - lib/grape/serve_stream/stream_response.rb
@@ -225,12 +235,10 @@ files:
225
235
  - lib/grape/util/lazy/base.rb
226
236
  - lib/grape/util/lazy/block.rb
227
237
  - lib/grape/util/lazy/value.rb
228
- - lib/grape/util/lazy/value_array.rb
229
- - lib/grape/util/lazy/value_enumerable.rb
230
- - lib/grape/util/lazy/value_hash.rb
231
238
  - lib/grape/util/media_type.rb
232
239
  - lib/grape/util/path_normalizer.rb
233
240
  - lib/grape/util/registry.rb
241
+ - lib/grape/util/scrub.rb
234
242
  - lib/grape/util/shadowed_rescue_handlers.rb
235
243
  - lib/grape/util/stackable_values.rb
236
244
  - lib/grape/util/translation.rb
@@ -238,13 +246,11 @@ files:
238
246
  - lib/grape/validations/attributes_iterator.rb
239
247
  - lib/grape/validations/coerce_options.rb
240
248
  - lib/grape/validations/contract_scope.rb
241
- - lib/grape/validations/multiple_attributes_iterator.rb
242
249
  - lib/grape/validations/oneof_collector.rb
243
250
  - lib/grape/validations/param_scope_tracker.rb
244
251
  - lib/grape/validations/params_documentation.rb
245
252
  - lib/grape/validations/params_scope.rb
246
253
  - lib/grape/validations/shared_options.rb
247
- - lib/grape/validations/single_attribute_iterator.rb
248
254
  - lib/grape/validations/types.rb
249
255
  - lib/grape/validations/types/array_coercer.rb
250
256
  - lib/grape/validations/types/custom_type_coercer.rb
@@ -268,6 +274,7 @@ files:
268
274
  - lib/grape/validations/validators/default_validator.rb
269
275
  - lib/grape/validations/validators/exactly_one_of_validator.rb
270
276
  - lib/grape/validations/validators/except_values_validator.rb
277
+ - lib/grape/validations/validators/hash_elements_validator.rb
271
278
  - lib/grape/validations/validators/length_validator.rb
272
279
  - lib/grape/validations/validators/multiple_params_base.rb
273
280
  - lib/grape/validations/validators/mutually_exclusive_validator.rb
@@ -283,9 +290,9 @@ licenses:
283
290
  - MIT
284
291
  metadata:
285
292
  bug_tracker_uri: https://github.com/ruby-grape/grape/issues
286
- changelog_uri: https://github.com/ruby-grape/grape/blob/v4.0.0/CHANGELOG.md
287
- documentation_uri: https://www.rubydoc.info/gems/grape/4.0.0
288
- source_code_uri: https://github.com/ruby-grape/grape/tree/v4.0.0
293
+ changelog_uri: https://github.com/ruby-grape/grape/blob/v4.1.0/CHANGELOG.md
294
+ documentation_uri: https://www.rubydoc.info/gems/grape/4.1.0
295
+ source_code_uri: https://github.com/ruby-grape/grape/tree/v4.1.0
289
296
  rubygems_mfa_required: 'true'
290
297
  rdoc_options: []
291
298
  require_paths:
@@ -294,14 +301,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
294
301
  requirements:
295
302
  - - ">="
296
303
  - !ruby/object:Gem::Version
297
- version: '3.3'
304
+ version: 3.3.1
298
305
  required_rubygems_version: !ruby/object:Gem::Requirement
299
306
  requirements:
300
307
  - - ">="
301
308
  - !ruby/object:Gem::Version
302
309
  version: '0'
303
310
  requirements: []
304
- rubygems_version: 4.0.16
311
+ rubygems_version: 4.0.20
305
312
  specification_version: 4
306
313
  summary: A simple Ruby framework for building REST-like APIs.
307
314
  test_files: []
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Util
5
- module Lazy
6
- class ValueArray < ValueEnumerable
7
- def initialize(array)
8
- super
9
- @value_hash = []
10
- array.each_with_index do |value, index|
11
- self[index] = value
12
- end
13
- end
14
-
15
- def evaluate
16
- @value_hash.map(&:evaluate)
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Util
5
- module Lazy
6
- class ValueEnumerable < Value
7
- def [](key)
8
- return Value.new(nil).reached_by(access_keys, key) if @value_hash[key].nil?
9
-
10
- @value_hash[key].reached_by(access_keys, key)
11
- end
12
-
13
- def fetch(access_keys)
14
- access_keys.reduce(self) { |node, key| node[key] }
15
- end
16
-
17
- def []=(key, value)
18
- value_class = case value
19
- when Hash
20
- ValueHash
21
- when Array
22
- ValueArray
23
- else
24
- Value
25
- end
26
- @value_hash[key] = value_class.new(value)
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Util
5
- module Lazy
6
- class ValueHash < ValueEnumerable
7
- def initialize(hash)
8
- super
9
- @value_hash = ActiveSupport::HashWithIndifferentAccess.new
10
- hash.each do |key, value|
11
- self[key] = value
12
- end
13
- end
14
-
15
- def evaluate
16
- @value_hash.transform_values(&:evaluate)
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Validations
5
- class MultipleAttributesIterator < AttributesIterator
6
- private
7
-
8
- def yield_attributes(resource_params)
9
- yield resource_params unless skip?(resource_params)
10
- end
11
- end
12
- end
13
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Validations
5
- class SingleAttributeIterator < AttributesIterator
6
- private
7
-
8
- def yield_attributes(val)
9
- return if skip?(val)
10
-
11
- # The emptiness of the scope's params is the same answer for every
12
- # attribute in it, and answering it costs a +respond_to?+, so it is
13
- # asked once for the whole list rather than once per attribute.
14
- empty_val = empty?(val)
15
- @attrs.each { |attr_name| yield val, attr_name, empty_val }
16
- end
17
-
18
- # Primitives like Integers and Booleans don't respond to +empty?+.
19
- # It could be possible to use +blank?+ instead, but
20
- #
21
- # false.blank?
22
- # => true
23
- def empty?(val)
24
- val.respond_to?(:empty?) ? val.empty? : val.nil?
25
- end
26
- end
27
- end
28
- end