grape 1.3.0 → 1.3.2

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +4 -6
  4. data/UPGRADING.md +61 -16
  5. data/lib/grape/api/instance.rb +11 -7
  6. data/lib/grape/api.rb +2 -2
  7. data/lib/grape/content_types.rb +34 -0
  8. data/lib/grape/dsl/helpers.rb +1 -1
  9. data/lib/grape/dsl/inside_route.rb +10 -9
  10. data/lib/grape/dsl/parameters.rb +4 -4
  11. data/lib/grape/dsl/routing.rb +6 -4
  12. data/lib/grape/exceptions/base.rb +0 -4
  13. data/lib/grape/exceptions/validation_errors.rb +11 -12
  14. data/lib/grape/http/headers.rb +26 -0
  15. data/lib/grape/middleware/base.rb +1 -3
  16. data/lib/grape/middleware/stack.rb +2 -1
  17. data/lib/grape/middleware/versioner/header.rb +4 -4
  18. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  19. data/lib/grape/middleware/versioner/path.rb +1 -1
  20. data/lib/grape/namespace.rb +12 -2
  21. data/lib/grape/path.rb +13 -3
  22. data/lib/grape/request.rb +12 -7
  23. data/lib/grape/router/pattern.rb +17 -16
  24. data/lib/grape/router/route.rb +4 -5
  25. data/lib/grape/router.rb +24 -14
  26. data/lib/grape/util/base_inheritable.rb +13 -6
  27. data/lib/grape/util/cache.rb +20 -0
  28. data/lib/grape/util/lazy_object.rb +43 -0
  29. data/lib/grape/util/reverse_stackable_values.rb +2 -0
  30. data/lib/grape/util/stackable_values.rb +7 -20
  31. data/lib/grape/validations/params_scope.rb +1 -1
  32. data/lib/grape/validations/types/build_coercer.rb +4 -3
  33. data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
  34. data/lib/grape/validations/types/file.rb +15 -12
  35. data/lib/grape/validations/types/json.rb +40 -36
  36. data/lib/grape/validations/types/primitive_coercer.rb +6 -3
  37. data/lib/grape/validations/types.rb +6 -5
  38. data/lib/grape/validations/validators/coerce.rb +14 -12
  39. data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
  40. data/lib/grape/validations/validators/regexp.rb +1 -1
  41. data/lib/grape/version.rb +1 -1
  42. data/lib/grape.rb +2 -3
  43. data/spec/grape/api_spec.rb +7 -6
  44. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  45. data/spec/grape/middleware/formatter_spec.rb +2 -2
  46. data/spec/grape/middleware/stack_spec.rb +9 -0
  47. data/spec/grape/path_spec.rb +4 -4
  48. data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
  49. data/spec/grape/validations/types/primitive_coercer_spec.rb +75 -0
  50. data/spec/grape/validations/types_spec.rb +1 -1
  51. data/spec/grape/validations/validators/coerce_spec.rb +160 -78
  52. data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
  53. data/spec/grape/validations_spec.rb +8 -12
  54. data/spec/spec_helper.rb +3 -0
  55. data/spec/support/eager_load.rb +19 -0
  56. metadata +12 -6
  57. data/lib/grape/util/content_types.rb +0 -28
data/lib/grape/path.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'grape/util/cache'
4
+
3
5
  module Grape
4
6
  # Represents a path to an endpoint.
5
7
  class Path
@@ -40,11 +42,11 @@ module Grape
40
42
  end
41
43
 
42
44
  def namespace?
43
- namespace && namespace.to_s =~ /^\S/ && namespace != '/'
45
+ namespace&.match?(/^\S/) && namespace != '/'
44
46
  end
45
47
 
46
48
  def path?
47
- raw_path && raw_path.to_s =~ /^\S/ && raw_path != '/'
49
+ raw_path&.match?(/^\S/) && raw_path != '/'
48
50
  end
49
51
 
50
52
  def suffix
@@ -58,7 +60,7 @@ module Grape
58
60
  end
59
61
 
60
62
  def path
61
- Grape::Router.normalize_path(parts.join('/'))
63
+ Grape::Router.normalize_path(PartsCache[parts])
62
64
  end
63
65
 
64
66
  def path_with_suffix
@@ -71,6 +73,14 @@ module Grape
71
73
 
72
74
  private
73
75
 
76
+ class PartsCache < Grape::Util::Cache
77
+ def initialize
78
+ @cache = Hash.new do |h, parts|
79
+ h[parts] = -parts.join('/')
80
+ end
81
+ end
82
+ end
83
+
74
84
  def parts
75
85
  parts = [mount_path, root_prefix].compact
76
86
  parts << ':version' if uses_path_versioning?
data/lib/grape/request.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'grape/util/lazy_object'
4
+
3
5
  module Grape
4
6
  class Request < Rack::Request
5
7
  HTTP_PREFIX = 'HTTP_'
@@ -30,14 +32,17 @@ module Grape
30
32
  end
31
33
 
32
34
  def build_headers
33
- headers = {}
34
- env.each_pair do |k, v|
35
- next unless k.to_s.start_with? HTTP_PREFIX
36
-
37
- k = k[5..-1].split('_').each(&:capitalize!).join('-')
38
- headers[k] = v
35
+ Grape::Util::LazyObject.new do
36
+ env.each_pair.with_object({}) do |(k, v), headers|
37
+ next unless k.to_s.start_with? HTTP_PREFIX
38
+ transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
39
+ headers[transformed_header] = v
40
+ end
39
41
  end
40
- headers
42
+ end
43
+
44
+ def transform_header(header)
45
+ -header[5..-1].split('_').each(&:capitalize!).join('-')
41
46
  end
42
47
  end
43
48
  end
@@ -2,35 +2,32 @@
2
2
 
3
3
  require 'forwardable'
4
4
  require 'mustermann/grape'
5
+ require 'grape/util/cache'
5
6
 
6
7
  module Grape
7
8
  class Router
8
9
  class Pattern
9
- DEFAULT_PATTERN_OPTIONS = { uri_decode: true, type: :grape }.freeze
10
+ DEFAULT_PATTERN_OPTIONS = { uri_decode: true }.freeze
10
11
  DEFAULT_SUPPORTED_CAPTURE = %i[format version].freeze
11
12
 
12
- attr_reader :origin, :path, :capture, :pattern
13
+ attr_reader :origin, :path, :pattern, :to_regexp
13
14
 
14
15
  extend Forwardable
15
16
  def_delegators :pattern, :named_captures, :params
16
- def_delegators :@regexp, :===
17
+ def_delegators :to_regexp, :===
17
18
  alias match? ===
18
19
 
19
20
  def initialize(pattern, **options)
20
21
  @origin = pattern
21
22
  @path = build_path(pattern, **options)
22
- @capture = extract_capture(**options)
23
- @pattern = Mustermann.new(@path, **pattern_options)
24
- @regexp = to_regexp
25
- end
26
-
27
- def to_regexp
28
- @to_regexp ||= @pattern.to_regexp
23
+ @pattern = Mustermann::Grape.new(@path, **pattern_options(options))
24
+ @to_regexp = @pattern.to_regexp
29
25
  end
30
26
 
31
27
  private
32
28
 
33
- def pattern_options
29
+ def pattern_options(options)
30
+ capture = extract_capture(**options)
34
31
  options = DEFAULT_PATTERN_OPTIONS.dup
35
32
  options[:capture] = capture if capture.present?
36
33
  options
@@ -43,23 +40,27 @@ module Grape
43
40
  pattern << '*path'
44
41
  end
45
42
 
46
- pattern = pattern.split('/').tap do |parts|
43
+ pattern = -pattern.split('/').tap do |parts|
47
44
  parts[parts.length - 1] = '?' + parts.last
48
45
  end.join('/') if pattern.end_with?('*path')
49
46
 
50
- "#{pattern}#{suffix}"
47
+ PatternCache[[pattern, suffix]]
51
48
  end
52
49
 
53
50
  def extract_capture(requirements: {}, **options)
54
51
  requirements = {}.merge(requirements)
55
- supported_capture.each_with_object(requirements) do |field, capture|
52
+ DEFAULT_SUPPORTED_CAPTURE.each_with_object(requirements) do |field, capture|
56
53
  option = Array(options[field])
57
54
  capture[field] = option.map(&:to_s) if option.present?
58
55
  end
59
56
  end
60
57
 
61
- def supported_capture
62
- DEFAULT_SUPPORTED_CAPTURE
58
+ class PatternCache < Grape::Util::Cache
59
+ def initialize
60
+ @cache = Hash.new do |h, (pattern, suffix)|
61
+ h[[pattern, suffix]] = -"#{pattern}#{suffix}"
62
+ end
63
+ end
63
64
  end
64
65
  end
65
66
  end
@@ -8,11 +8,11 @@ require 'pathname'
8
8
  module Grape
9
9
  class Router
10
10
  class Route
11
- ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/
12
- SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/
11
+ ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/.freeze
12
+ SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
13
13
  FIXED_NAMED_CAPTURES = %w[format version].freeze
14
14
 
15
- attr_accessor :pattern, :translator, :app, :index, :regexp, :options
15
+ attr_accessor :pattern, :translator, :app, :index, :options
16
16
 
17
17
  alias attributes translator
18
18
 
@@ -31,7 +31,7 @@ module Grape
31
31
  end
32
32
 
33
33
  def respond_to_missing?(method_id, _)
34
- ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
34
+ ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
35
35
  end
36
36
 
37
37
  %i[
@@ -67,7 +67,6 @@ module Grape
67
67
  method_s = method.to_s
68
68
  method_upcase = Grape::Http::Headers.find_supported_method(method_s) || method_s.upcase
69
69
 
70
- @suffix = options[:suffix]
71
70
  @options = options.merge(method: method_upcase)
72
71
  @pattern = Pattern.new(pattern, **options)
73
72
  @translator = AttributeTranslator.new(**options, request_method: method_upcase)
data/lib/grape/router.rb CHANGED
@@ -1,27 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'grape/router/route'
4
+ require 'grape/util/cache'
4
5
 
5
6
  module Grape
6
7
  class Router
7
8
  attr_reader :map, :compiled
8
9
 
9
10
  class Any < AttributeTranslator
10
- attr_reader :pattern, :regexp, :index
11
- def initialize(pattern, regexp, index, **attributes)
11
+ attr_reader :pattern, :index
12
+ def initialize(pattern, index, **attributes)
12
13
  @pattern = pattern
13
- @regexp = regexp
14
14
  @index = index
15
15
  super(attributes)
16
16
  end
17
17
  end
18
18
 
19
+ class NormalizePathCache < Grape::Util::Cache
20
+ def initialize
21
+ @cache = Hash.new do |h, path|
22
+ normalized_path = +"/#{path}"
23
+ normalized_path.squeeze!('/')
24
+ normalized_path.sub!(%r{/+\Z}, '')
25
+ normalized_path = '/' if normalized_path.empty?
26
+ h[path] = -normalized_path
27
+ end
28
+ end
29
+ end
30
+
19
31
  def self.normalize_path(path)
20
- path = +"/#{path}"
21
- path.squeeze!('/')
22
- path.sub!(%r{/+\Z}, '')
23
- path = '/' if path == ''
24
- path
32
+ NormalizePathCache[path]
25
33
  end
26
34
 
27
35
  def self.supported_methods
@@ -30,18 +38,20 @@ module Grape
30
38
 
31
39
  def initialize
32
40
  @neutral_map = []
41
+ @neutral_regexes = []
33
42
  @map = Hash.new { |hash, key| hash[key] = [] }
34
43
  @optimized_map = Hash.new { |hash, key| hash[key] = // }
35
44
  end
36
45
 
37
46
  def compile!
38
47
  return if compiled
39
- @union = Regexp.union(@neutral_map.map(&:regexp))
48
+ @union = Regexp.union(@neutral_regexes)
49
+ @neutral_regexes = nil
40
50
  self.class.supported_methods.each do |method|
41
51
  routes = map[method]
42
52
  @optimized_map[method] = routes.map.with_index do |route, index|
43
53
  route.index = index
44
- route.regexp = Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})")
54
+ Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})")
45
55
  end
46
56
  @optimized_map[method] = Regexp.union(@optimized_map[method])
47
57
  end
@@ -53,8 +63,8 @@ module Grape
53
63
  end
54
64
 
55
65
  def associate_routes(pattern, **options)
56
- regexp = Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
57
- @neutral_map << Any.new(pattern, regexp, @neutral_map.length, **options)
66
+ @neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
67
+ @neutral_map << Any.new(pattern, @neutral_map.length, **options)
58
68
  end
59
69
 
60
70
  def call(env)
@@ -106,7 +116,7 @@ module Grape
106
116
  env,
107
117
  neighbor.allow_header,
108
118
  neighbor.endpoint
109
- ) if neighbor && method == 'OPTIONS' && !cascade
119
+ ) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade
110
120
 
111
121
  route = match?(input, '*')
112
122
  return neighbor.endpoint.call(env) if neighbor && cascade && route
@@ -160,7 +170,7 @@ module Grape
160
170
  end
161
171
 
162
172
  def call_with_allow_headers(env, methods, endpoint)
163
- env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods
173
+ env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ').freeze
164
174
  endpoint.call(env)
165
175
  end
166
176
 
@@ -5,8 +5,7 @@ module Grape
5
5
  # Base for classes which need to operate with own values kept
6
6
  # in the hash and inherited values kept in a Hash-like object.
7
7
  class BaseInheritable
8
- attr_accessor :inherited_values
9
- attr_accessor :new_values
8
+ attr_accessor :inherited_values, :new_values
10
9
 
11
10
  # @param inherited_values [Object] An object implementing an interface
12
11
  # of the Hash class.
@@ -26,10 +25,18 @@ module Grape
26
25
  end
27
26
 
28
27
  def keys
29
- combined = inherited_values.keys
30
- combined.concat(new_values.keys)
31
- combined.uniq!
32
- combined
28
+ if new_values.any?
29
+ combined = inherited_values.keys
30
+ combined.concat(new_values.keys)
31
+ combined.uniq!
32
+ combined
33
+ else
34
+ inherited_values.keys
35
+ end
36
+ end
37
+
38
+ def key?(name)
39
+ inherited_values.key?(name) || new_values.key?(name)
33
40
  end
34
41
  end
35
42
  end
@@ -0,0 +1,20 @@
1
+ # frozen_String_literal: true
2
+
3
+ require 'singleton'
4
+ require 'forwardable'
5
+
6
+ module Grape
7
+ module Util
8
+ class Cache
9
+ include Singleton
10
+
11
+ attr_reader :cache
12
+
13
+ class << self
14
+ extend Forwardable
15
+ def_delegators :cache, :[]
16
+ def_delegators :instance, :cache
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Based on https://github.com/HornsAndHooves/lazy_object
4
+
5
+ module Grape
6
+ module Util
7
+ class LazyObject < BasicObject
8
+ attr_reader :callable
9
+
10
+ def initialize(&callable)
11
+ @callable = callable
12
+ end
13
+
14
+ def __target_object__
15
+ @__target_object__ ||= callable.call
16
+ end
17
+
18
+ def ==(other)
19
+ __target_object__ == other
20
+ end
21
+
22
+ def !=(other)
23
+ __target_object__ != other
24
+ end
25
+
26
+ def !
27
+ !__target_object__
28
+ end
29
+
30
+ def method_missing(method_name, *args, &block)
31
+ if __target_object__.respond_to?(method_name)
32
+ __target_object__.send(method_name, *args, &block)
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def respond_to_missing?(method_name, include_priv = false)
39
+ __target_object__.respond_to?(method_name, include_priv)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -8,6 +8,8 @@ module Grape
8
8
  protected
9
9
 
10
10
  def concat_values(inherited_value, new_value)
11
+ return inherited_value unless new_value
12
+
11
13
  [].tap do |value|
12
14
  value.concat(new_value)
13
15
  value.concat(inherited_value)
@@ -5,30 +5,19 @@ require_relative 'base_inheritable'
5
5
  module Grape
6
6
  module Util
7
7
  class StackableValues < BaseInheritable
8
- attr_reader :frozen_values
9
-
10
- def initialize(*_args)
11
- super
12
-
13
- @frozen_values = {}
14
- end
15
-
16
8
  # Even if there is no value, an empty array will be returned.
17
9
  def [](name)
18
- return @frozen_values[name] if @frozen_values.key? name
10
+ inherited_value = inherited_values[name]
11
+ new_value = new_values[name]
19
12
 
20
- inherited_value = @inherited_values[name]
21
- new_value = @new_values[name] || []
22
-
23
- return new_value unless inherited_value
13
+ return new_value || [] unless inherited_value
24
14
 
25
15
  concat_values(inherited_value, new_value)
26
16
  end
27
17
 
28
18
  def []=(name, value)
29
- raise if @frozen_values.key? name
30
- @new_values[name] ||= []
31
- @new_values[name].push value
19
+ new_values[name] ||= []
20
+ new_values[name].push value
32
21
  end
33
22
 
34
23
  def to_hash
@@ -37,13 +26,11 @@ module Grape
37
26
  end
38
27
  end
39
28
 
40
- def freeze_value(key)
41
- @frozen_values[key] = self[key].freeze
42
- end
43
-
44
29
  protected
45
30
 
46
31
  def concat_values(inherited_value, new_value)
32
+ return inherited_value unless new_value
33
+
47
34
  [].tap do |value|
48
35
  value.concat(inherited_value)
49
36
  value.concat(new_value)
@@ -244,7 +244,7 @@ module Grape
244
244
  end
245
245
 
246
246
  def validates(attrs, validations)
247
- doc_attrs = { required: validations.keys.include?(:presence) }
247
+ doc_attrs = { required: validations.key?(:presence) }
248
248
 
249
249
  coerce_type = infer_coercion(validations)
250
250
 
@@ -42,6 +42,9 @@ module Grape
42
42
  end
43
43
 
44
44
  def self.create_coercer_instance(type, method, strict)
45
+ # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
46
+ type = Types.map_special(type)
47
+
45
48
  # Use a special coercer for multiply-typed parameters.
46
49
  if Types.multiple?(type)
47
50
  MultipleTypeCoercer.new(type, method)
@@ -55,10 +58,8 @@ module Grape
55
58
  # method is supplied.
56
59
  elsif Types.collection_of_custom?(type)
57
60
  Types::CustomTypeCollectionCoercer.new(
58
- type.first, type.is_a?(Set)
61
+ Types.map_special(type.first), type.is_a?(Set)
59
62
  )
60
- elsif Types.special?(type)
61
- Types::SPECIAL[type].new
62
63
  elsif type.is_a?(Array)
63
64
  ArrayCoercer.new type, strict
64
65
  elsif type.is_a?(Set)
@@ -60,7 +60,7 @@ module Grape
60
60
  end
61
61
 
62
62
  def coerced?(val)
63
- @type_check.call val
63
+ val.nil? || @type_check.call(val)
64
64
  end
65
65
 
66
66
  private
@@ -7,20 +7,23 @@ module Grape
7
7
  # Actual handling of these objects is provided by +Rack::Request+;
8
8
  # this class is here only to assert that rack's handling has succeeded.
9
9
  class File
10
- def call(input)
11
- return InvalidValue.new unless coerced?(input)
10
+ class << self
11
+ def parse(input)
12
+ return if input.nil?
13
+ return InvalidValue.new unless parsed?(input)
12
14
 
13
- # Processing of multipart file objects
14
- # is already taken care of by Rack::Request.
15
- # Nothing to do here.
16
- input
17
- end
15
+ # Processing of multipart file objects
16
+ # is already taken care of by Rack::Request.
17
+ # Nothing to do here.
18
+ input
19
+ end
18
20
 
19
- def coerced?(value)
20
- # Rack::Request creates a Hash with filename,
21
- # content type and an IO object. Do a bit of basic
22
- # duck-typing.
23
- value.is_a?(::Hash) && value.key?(:tempfile) && value[:tempfile].is_a?(Tempfile)
21
+ def parsed?(value)
22
+ # Rack::Request creates a Hash with filename,
23
+ # content type and an IO object. Do a bit of basic
24
+ # duck-typing.
25
+ value.is_a?(::Hash) && value.key?(:tempfile) && value[:tempfile].is_a?(Tempfile)
26
+ end
24
27
  end
25
28
  end
26
29
  end
@@ -12,35 +12,37 @@ module Grape
12
12
  # validation system will apply nested validation rules to
13
13
  # all returned objects.
14
14
  class Json
15
- # Coerce the input into a JSON-like data structure.
16
- #
17
- # @param input [String] a JSON-encoded parameter value
18
- # @return [Hash,Array<Hash>,nil]
19
- def call(input)
20
- return input if coerced?(input)
15
+ class << self
16
+ # Coerce the input into a JSON-like data structure.
17
+ #
18
+ # @param input [String] a JSON-encoded parameter value
19
+ # @return [Hash,Array<Hash>,nil]
20
+ def parse(input)
21
+ return input if parsed?(input)
21
22
 
22
- # Allow nulls and blank strings
23
- return if input.nil? || input =~ /^\s*$/
24
- JSON.parse(input, symbolize_names: true)
25
- end
23
+ # Allow nulls and blank strings
24
+ return if input.nil? || input.match?(/^\s*$/)
25
+ JSON.parse(input, symbolize_names: true)
26
+ end
26
27
 
27
- # Checks that the input was parsed successfully
28
- # and isn't something odd such as an array of primitives.
29
- #
30
- # @param value [Object] result of {#coerce}
31
- # @return [true,false]
32
- def coerced?(value)
33
- value.is_a?(::Hash) || coerced_collection?(value)
34
- end
28
+ # Checks that the input was parsed successfully
29
+ # and isn't something odd such as an array of primitives.
30
+ #
31
+ # @param value [Object] result of {#parse}
32
+ # @return [true,false]
33
+ def parsed?(value)
34
+ value.is_a?(::Hash) || coerced_collection?(value)
35
+ end
35
36
 
36
- protected
37
+ protected
37
38
 
38
- # Is the value an array of JSON-like objects?
39
- #
40
- # @param value [Object] result of {#coerce}
41
- # @return [true,false]
42
- def coerced_collection?(value)
43
- value.is_a?(::Array) && value.all? { |i| i.is_a? ::Hash }
39
+ # Is the value an array of JSON-like objects?
40
+ #
41
+ # @param value [Object] result of {#parse}
42
+ # @return [true,false]
43
+ def coerced_collection?(value)
44
+ value.is_a?(::Array) && value.all? { |i| i.is_a? ::Hash }
45
+ end
44
46
  end
45
47
  end
46
48
 
@@ -49,18 +51,20 @@ module Grape
49
51
  # objects and arrays of objects, but wraps single objects
50
52
  # in an Array.
51
53
  class JsonArray < Json
52
- # See {Json#coerce}. Wraps single objects in an array.
53
- #
54
- # @param input [String] JSON-encoded parameter value
55
- # @return [Array<Hash>]
56
- def call(input)
57
- json = super
58
- Array.wrap(json) unless json.nil?
59
- end
54
+ class << self
55
+ # See {Json#parse}. Wraps single objects in an array.
56
+ #
57
+ # @param input [String] JSON-encoded parameter value
58
+ # @return [Array<Hash>]
59
+ def parse(input)
60
+ json = super
61
+ Array.wrap(json) unless json.nil?
62
+ end
60
63
 
61
- # See {Json#coerced_collection?}
62
- def coerced?(value)
63
- coerced_collection? value
64
+ # See {Json#coerced_collection?}
65
+ def parsed?(value)
66
+ coerced_collection? value
67
+ end
64
68
  end
65
69
  end
66
70
  end
@@ -6,17 +6,20 @@ module Grape
6
6
  module Validations
7
7
  module Types
8
8
  # Coerces the given value to a type defined via a +type+ argument during
9
- # initialization.
9
+ # initialization. When +strict+ is true, it doesn't coerce a value but check
10
+ # that it has the proper type.
10
11
  class PrimitiveCoercer < DryTypeCoercer
11
12
  MAPPING = {
12
13
  Grape::API::Boolean => DryTypes::Params::Bool,
14
+ BigDecimal => DryTypes::Params::Decimal,
13
15
 
14
- # unfortunatelly, a +Params+ scope doesn't contain String
16
+ # unfortunately, a +Params+ scope doesn't contain String
15
17
  String => DryTypes::Coercible::String
16
18
  }.freeze
17
19
 
18
20
  STRICT_MAPPING = {
19
- Grape::API::Boolean => DryTypes::Strict::Bool
21
+ Grape::API::Boolean => DryTypes::Strict::Bool,
22
+ BigDecimal => DryTypes::Strict::Decimal
20
23
  }.freeze
21
24
 
22
25
  def initialize(type, strict = false)