grape 2.0.0 → 2.1.1

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -1
  3. data/README.md +362 -316
  4. data/UPGRADING.md +197 -7
  5. data/grape.gemspec +5 -6
  6. data/lib/grape/api/instance.rb +13 -10
  7. data/lib/grape/api.rb +17 -8
  8. data/lib/grape/content_types.rb +0 -2
  9. data/lib/grape/cookies.rb +2 -1
  10. data/lib/grape/dry_types.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +22 -20
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/inside_route.rb +42 -13
  14. data/lib/grape/dsl/parameters.rb +4 -3
  15. data/lib/grape/dsl/routing.rb +20 -4
  16. data/lib/grape/dsl/validations.rb +13 -0
  17. data/lib/grape/endpoint.rb +12 -15
  18. data/lib/grape/{util/env.rb → env.rb} +0 -5
  19. data/lib/grape/error_formatter/txt.rb +11 -10
  20. data/lib/grape/exceptions/base.rb +3 -3
  21. data/lib/grape/exceptions/validation.rb +0 -2
  22. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  23. data/lib/grape/exceptions/validation_errors.rb +1 -3
  24. data/lib/grape/extensions/hash.rb +5 -1
  25. data/lib/grape/http/headers.rb +18 -34
  26. data/lib/grape/{util/json.rb → json.rb} +1 -3
  27. data/lib/grape/locale/en.yml +3 -0
  28. data/lib/grape/middleware/auth/base.rb +0 -2
  29. data/lib/grape/middleware/auth/dsl.rb +0 -2
  30. data/lib/grape/middleware/base.rb +0 -2
  31. data/lib/grape/middleware/error.rb +55 -50
  32. data/lib/grape/middleware/formatter.rb +16 -13
  33. data/lib/grape/middleware/globals.rb +1 -3
  34. data/lib/grape/middleware/stack.rb +2 -3
  35. data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
  36. data/lib/grape/middleware/versioner/header.rb +17 -163
  37. data/lib/grape/middleware/versioner/param.rb +2 -4
  38. data/lib/grape/middleware/versioner/path.rb +1 -3
  39. data/lib/grape/namespace.rb +3 -4
  40. data/lib/grape/path.rb +24 -29
  41. data/lib/grape/request.rb +4 -12
  42. data/lib/grape/router/base_route.rb +39 -0
  43. data/lib/grape/router/greedy_route.rb +20 -0
  44. data/lib/grape/router/pattern.rb +39 -30
  45. data/lib/grape/router/route.rb +22 -59
  46. data/lib/grape/router.rb +32 -37
  47. data/lib/grape/util/accept/header.rb +19 -0
  48. data/lib/grape/util/accept_header_handler.rb +105 -0
  49. data/lib/grape/util/base_inheritable.rb +4 -4
  50. data/lib/grape/util/cache.rb +0 -3
  51. data/lib/grape/util/endpoint_configuration.rb +1 -1
  52. data/lib/grape/util/header.rb +13 -0
  53. data/lib/grape/util/inheritable_values.rb +0 -2
  54. data/lib/grape/util/lazy/block.rb +29 -0
  55. data/lib/grape/util/lazy/object.rb +45 -0
  56. data/lib/grape/util/lazy/value.rb +38 -0
  57. data/lib/grape/util/lazy/value_array.rb +21 -0
  58. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  59. data/lib/grape/util/lazy/value_hash.rb +21 -0
  60. data/lib/grape/util/media_type.rb +70 -0
  61. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  62. data/lib/grape/util/stackable_values.rb +1 -6
  63. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  64. data/lib/grape/validations/attributes_doc.rb +38 -36
  65. data/lib/grape/validations/contract_scope.rb +71 -0
  66. data/lib/grape/validations/params_scope.rb +10 -9
  67. data/lib/grape/validations/types/array_coercer.rb +0 -2
  68. data/lib/grape/validations/types/build_coercer.rb +69 -71
  69. data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
  70. data/lib/grape/validations/types/json.rb +0 -2
  71. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  72. data/lib/grape/validations/types/set_coercer.rb +0 -3
  73. data/lib/grape/validations/types.rb +0 -3
  74. data/lib/grape/validations/validators/base.rb +1 -0
  75. data/lib/grape/validations/validators/default_validator.rb +5 -1
  76. data/lib/grape/validations/validators/length_validator.rb +42 -0
  77. data/lib/grape/validations/validators/values_validator.rb +6 -1
  78. data/lib/grape/validations.rb +3 -7
  79. data/lib/grape/version.rb +1 -1
  80. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  81. data/lib/grape.rb +30 -274
  82. metadata +31 -37
  83. data/lib/grape/eager_load.rb +0 -20
  84. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  85. data/lib/grape/router/attribute_translator.rb +0 -63
  86. data/lib/grape/util/lazy_block.rb +0 -27
  87. data/lib/grape/util/lazy_object.rb +0 -43
  88. data/lib/grape/util/lazy_value.rb +0 -91
@@ -2,56 +2,58 @@
2
2
 
3
3
  module Grape
4
4
  module Validations
5
- class ParamsScope
6
- # Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
7
- # internal API), the class only cleans up attributes to avoid junk in RAM.
8
- class AttributesDoc
9
- attr_accessor :type, :values
10
-
11
- # @param api [Grape::API::Instance]
12
- # @param scope [Validations::ParamsScope]
13
- def initialize(api, scope)
14
- @api = api
15
- @scope = scope
16
- @type = type
17
- end
5
+ # Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
6
+ # internal API), the class only cleans up attributes to avoid junk in RAM.
7
+
8
+ class AttributesDoc
9
+ attr_accessor :type, :values
10
+
11
+ # @param api [Grape::API::Instance]
12
+ # @param scope [Validations::ParamsScope]
13
+ def initialize(api, scope)
14
+ @api = api
15
+ @scope = scope
16
+ @type = type
17
+ end
18
18
 
19
- def extract_details(validations)
20
- details[:required] = validations.key?(:presence)
19
+ def extract_details(validations)
20
+ details[:required] = validations.key?(:presence)
21
21
 
22
- desc = validations.delete(:desc) || validations.delete(:description)
22
+ desc = validations.delete(:desc) || validations.delete(:description)
23
23
 
24
- details[:desc] = desc if desc
24
+ details[:desc] = desc if desc
25
25
 
26
- documentation = validations.delete(:documentation)
26
+ documentation = validations.delete(:documentation)
27
27
 
28
- details[:documentation] = documentation if documentation
28
+ details[:documentation] = documentation if documentation
29
29
 
30
- details[:default] = validations[:default] if validations.key?(:default)
31
- end
30
+ details[:default] = validations[:default] if validations.key?(:default)
32
31
 
33
- def document(attrs)
34
- return if @api.namespace_inheritable(:do_not_document)
32
+ details[:min_length] = validations[:length][:min] if validations.key?(:length) && validations[:length].key?(:min)
33
+ details[:max_length] = validations[:length][:max] if validations.key?(:length) && validations[:length].key?(:max)
34
+ end
35
35
 
36
- details[:type] = type.to_s if type
37
- details[:values] = values if values
36
+ def document(attrs)
37
+ return if @api.namespace_inheritable(:do_not_document)
38
38
 
39
- documented_attrs = attrs.each_with_object({}) do |name, memo|
40
- memo[@scope.full_name(name)] = details
41
- end
39
+ details[:type] = type.to_s if type
40
+ details[:values] = values if values
42
41
 
43
- @api.namespace_stackable(:params, documented_attrs)
42
+ documented_attrs = attrs.each_with_object({}) do |name, memo|
43
+ memo[@scope.full_name(name)] = details
44
44
  end
45
45
 
46
- def required
47
- details[:required]
48
- end
46
+ @api.namespace_stackable(:params, documented_attrs)
47
+ end
48
+
49
+ def required
50
+ details[:required]
51
+ end
49
52
 
50
- protected
53
+ protected
51
54
 
52
- def details
53
- @details ||= {}
54
- end
55
+ def details
56
+ @details ||= {}
55
57
  end
56
58
  end
57
59
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ class ContractScope
6
+ # Declare the contract to be used for the endpoint's parameters.
7
+ # @param api [API] the API endpoint to modify.
8
+ # @param contract the contract or schema to be used for validation. Optional.
9
+ # @yield a block yielding a new schema class. Optional.
10
+ def initialize(api, contract = nil, &block)
11
+ # When block is passed, the first arg is either schema or nil.
12
+ contract = Dry::Schema.Params(parent: contract, &block) if block
13
+
14
+ if contract.respond_to?(:schema)
15
+ # It's a Dry::Validation::Contract, then.
16
+ contract = contract.new
17
+ key_map = contract.schema.key_map
18
+ else
19
+ # Dry::Schema::Processor, hopefully.
20
+ key_map = contract.key_map
21
+ end
22
+
23
+ api.namespace_stackable(:contract_key_map, key_map)
24
+
25
+ validator_options = {
26
+ validator_class: Validator,
27
+ opts: { schema: contract }
28
+ }
29
+
30
+ api.namespace_stackable(:validations, validator_options)
31
+ end
32
+
33
+ class Validator
34
+ attr_reader :schema
35
+
36
+ def initialize(*_args, schema:)
37
+ @schema = schema
38
+ end
39
+
40
+ # Validates a given request.
41
+ # @param request [Grape::Request] the request currently being handled
42
+ # @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed
43
+ # @return [void]
44
+ def validate(request)
45
+ res = schema.call(request.params)
46
+
47
+ if res.success?
48
+ request.params.deep_merge!(res.to_h)
49
+ return
50
+ end
51
+
52
+ errors = []
53
+
54
+ res.errors.messages.each do |message|
55
+ full_name = message.path.first.to_s
56
+
57
+ full_name += "[#{message.path[1..].join('][')}]" if message.path.size > 1
58
+
59
+ errors << Grape::Exceptions::Validation.new(params: [full_name], message: message.text)
60
+ end
61
+
62
+ raise Grape::Exceptions::ValidationArrayErrors.new(errors)
63
+ end
64
+
65
+ def fail_fast?
66
+ false
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'attributes_doc'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  class ParamsScope
@@ -211,11 +209,11 @@ module Grape
211
209
 
212
210
  def require_required_and_optional_fields(context, opts)
213
211
  if context == :all
214
- optional_fields = Array(opts[:except])
215
- required_fields = opts[:using].keys - optional_fields
212
+ optional_fields = Array.wrap(opts[:except])
213
+ required_fields = opts[:using].keys.delete_if { |f| optional_fields.include?(f) }
216
214
  else # context == :none
217
- required_fields = Array(opts[:except])
218
- optional_fields = opts[:using].keys - required_fields
215
+ required_fields = Array.wrap(opts[:except])
216
+ optional_fields = opts[:using].keys.delete_if { |f| required_fields.include?(f) }
219
217
  end
220
218
  required_fields.each do |field|
221
219
  field_opts = opts[:using][field]
@@ -231,7 +229,10 @@ module Grape
231
229
 
232
230
  def require_optional_fields(context, opts)
233
231
  optional_fields = opts[:using].keys
234
- optional_fields -= Array(opts[:except]) unless context == :all
232
+ unless context == :all
233
+ except_fields = Array.wrap(opts[:except])
234
+ optional_fields.delete_if { |f| except_fields.include?(f) }
235
+ end
235
236
  optional_fields.each do |field|
236
237
  field_opts = opts[:using][field]
237
238
  optional(field, field_opts) if field_opts
@@ -266,6 +267,7 @@ module Grape
266
267
  parent: self,
267
268
  optional: optional,
268
269
  type: type || Array,
270
+ group: @group,
269
271
  &block
270
272
  )
271
273
  end
@@ -463,8 +465,7 @@ module Grape
463
465
  raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) }
464
466
 
465
467
  if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
466
- raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
467
-
468
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
468
469
  end
469
470
 
470
471
  return unless excepts && !excepts.is_a?(Proc)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'dry_type_coercer'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  module Types
@@ -1,94 +1,92 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'array_coercer'
4
- require_relative 'set_coercer'
5
- require_relative 'primitive_coercer'
6
-
7
3
  module Grape
8
4
  module Validations
9
5
  module Types
10
- # Chooses the best coercer for the given type. For example, if the type
11
- # is Integer, it will return a coercer which will be able to coerce a value
12
- # to the integer.
13
- #
14
- # There are a few very special coercers which might be returned.
15
- #
16
- # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
17
- # the given type implies values in an array with different types.
18
- # For example, +[Integer, String]+ allows integer and string values in
19
- # an array.
20
- #
21
- # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
22
- # a method is specified by a user with +coerce_with+ option or the user
23
- # specifies a custom type which implements requirments of
24
- # +Grape::Types::CustomTypeCoercer+.
25
- #
26
- # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
27
- # previous one, but it expects an array or set of values having a custom
28
- # type implemented by the user.
29
- #
30
- # There is also a group of custom types implemented by Grape, check
31
- # +Grape::Validations::Types::SPECIAL+ to get the full list.
32
- #
33
- # @param type [Class] the type to which input strings
34
- # should be coerced
35
- # @param method [Class,#call] the coercion method to use
36
- # @return [Object] object to be used
37
- # for coercion and type validation
38
- def self.build_coercer(type, method: nil, strict: false)
39
- cache_instance(type, method, strict) do
40
- create_coercer_instance(type, method, strict)
6
+ module BuildCoercer
7
+ # Chooses the best coercer for the given type. For example, if the type
8
+ # is Integer, it will return a coercer which will be able to coerce a value
9
+ # to the integer.
10
+ #
11
+ # There are a few very special coercers which might be returned.
12
+ #
13
+ # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
14
+ # the given type implies values in an array with different types.
15
+ # For example, +[Integer, String]+ allows integer and string values in
16
+ # an array.
17
+ #
18
+ # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
19
+ # a method is specified by a user with +coerce_with+ option or the user
20
+ # specifies a custom type which implements requirments of
21
+ # +Grape::Types::CustomTypeCoercer+.
22
+ #
23
+ # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
24
+ # previous one, but it expects an array or set of values having a custom
25
+ # type implemented by the user.
26
+ #
27
+ # There is also a group of custom types implemented by Grape, check
28
+ # +Grape::Validations::Types::SPECIAL+ to get the full list.
29
+ #
30
+ # @param type [Class] the type to which input strings
31
+ # should be coerced
32
+ # @param method [Class,#call] the coercion method to use
33
+ # @return [Object] object to be used
34
+ # for coercion and type validation
35
+ def self.build_coercer(type, method: nil, strict: false)
36
+ cache_instance(type, method, strict) do
37
+ create_coercer_instance(type, method, strict)
38
+ end
41
39
  end
42
- end
43
40
 
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)
41
+ def self.create_coercer_instance(type, method, strict)
42
+ # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
43
+ type = Types.map_special(type)
47
44
 
48
- # Use a special coercer for multiply-typed parameters.
49
- if Types.multiple?(type)
50
- MultipleTypeCoercer.new(type, method)
45
+ # Use a special coercer for multiply-typed parameters.
46
+ if Types.multiple?(type)
47
+ MultipleTypeCoercer.new(type, method)
51
48
 
52
- # Use a special coercer for custom types and coercion methods.
53
- elsif method || Types.custom?(type)
54
- CustomTypeCoercer.new(type, method)
49
+ # Use a special coercer for custom types and coercion methods.
50
+ elsif method || Types.custom?(type)
51
+ CustomTypeCoercer.new(type, method)
55
52
 
56
- # Special coercer for collections of types that implement a parse method.
57
- # CustomTypeCoercer (above) already handles such types when an explicit coercion
58
- # method is supplied.
59
- elsif Types.collection_of_custom?(type)
60
- Types::CustomTypeCollectionCoercer.new(
61
- Types.map_special(type.first), type.is_a?(Set)
62
- )
63
- else
64
- DryTypeCoercer.coercer_instance_for(type, strict)
53
+ # Special coercer for collections of types that implement a parse method.
54
+ # CustomTypeCoercer (above) already handles such types when an explicit coercion
55
+ # method is supplied.
56
+ elsif Types.collection_of_custom?(type)
57
+ Types::CustomTypeCollectionCoercer.new(
58
+ Types.map_special(type.first), type.is_a?(Set)
59
+ )
60
+ else
61
+ DryTypeCoercer.coercer_instance_for(type, strict)
62
+ end
65
63
  end
66
- end
67
64
 
68
- def self.cache_instance(type, method, strict, &_block)
69
- key = cache_key(type, method, strict)
65
+ def self.cache_instance(type, method, strict, &_block)
66
+ key = cache_key(type, method, strict)
70
67
 
71
- return @__cache[key] if @__cache.key?(key)
68
+ return @__cache[key] if @__cache.key?(key)
72
69
 
73
- instance = yield
70
+ instance = yield
74
71
 
75
- @__cache_write_lock.synchronize do
76
- @__cache[key] = instance
77
- end
72
+ @__cache_write_lock.synchronize do
73
+ @__cache[key] = instance
74
+ end
78
75
 
79
- instance
80
- end
76
+ instance
77
+ end
81
78
 
82
- def self.cache_key(type, method, strict)
83
- [type, method, strict].each_with_object(+'_') do |val, memo|
84
- next if val.nil?
79
+ def self.cache_key(type, method, strict)
80
+ [type, method, strict].each_with_object(+'_') do |val, memo|
81
+ next if val.nil?
85
82
 
86
- memo << '_' << val.to_s
83
+ memo << '_' << val.to_s
84
+ end
87
85
  end
88
- end
89
86
 
90
- instance_variable_set(:@__cache, {})
91
- instance_variable_set(:@__cache_write_lock, Mutex.new)
87
+ instance_variable_set(:@__cache, {})
88
+ instance_variable_set(:@__cache_write_lock, Mutex.new)
89
+ end
92
90
  end
93
91
  end
94
92
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-types'
4
-
5
3
  module DryTypes
6
4
  # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
7
5
  # a container in this case. Check documentation for more information
@@ -24,9 +22,7 @@ module Grape
24
22
  # collection_coercer_for(Array)
25
23
  # #=> Grape::Validations::Types::ArrayCoercer
26
24
  def collection_coercer_for(type)
27
- collection_coercers.fetch(type) do
28
- DryTypeCoercer.collection_coercers[type] = Grape::Validations::Types.const_get("#{type.name.camelize}Coercer")
29
- end
25
+ Grape::Validations::Types.const_get(:"#{type.name.camelize}Coercer")
30
26
  end
31
27
 
32
28
  # Returns an instance of a coercer for a given type
@@ -37,12 +33,6 @@ module Grape
37
33
  # so we need to figure out the actual type
38
34
  collection_coercer_for(type.class).new(type, strict)
39
35
  end
40
-
41
- protected
42
-
43
- def collection_coercers
44
- @collection_coercers ||= {}
45
- end
46
36
  end
47
37
 
48
38
  def initialize(type, strict = false)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  module Types
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'dry_type_coercer'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  module Types
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
- require_relative 'array_coercer'
5
-
6
3
  module Grape
7
4
  module Validations
8
5
  module Types
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/validations/types/json'
4
- require 'grape/validations/types/file'
5
-
6
3
  module Grape
7
4
  module Validations
8
5
  # Module for code related to grape's system for
@@ -59,6 +59,7 @@ module Grape
59
59
  end
60
60
 
61
61
  def self.inherited(klass)
62
+ super
62
63
  return if klass.name.blank?
63
64
 
64
65
  short_validator_name = klass.name.demodulize.underscore.delete_suffix('_validator')
@@ -11,7 +11,11 @@ module Grape
11
11
 
12
12
  def validate_param!(attr_name, params)
13
13
  params[attr_name] = if @default.is_a? Proc
14
- @default.call
14
+ if @default.parameters.empty?
15
+ @default.call
16
+ else
17
+ @default.call(params)
18
+ end
15
19
  elsif @default.frozen? || !@default.duplicable?
16
20
  @default
17
21
  else
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class LengthValidator < Base
7
+ def initialize(attrs, options, required, scope, **opts)
8
+ @min = options[:min]
9
+ @max = options[:max]
10
+
11
+ super
12
+
13
+ raise ArgumentError, 'min must be an integer greater than or equal to zero' if !@min.nil? && (!@min.is_a?(Integer) || @min.negative?)
14
+ raise ArgumentError, 'max must be an integer greater than or equal to zero' if !@max.nil? && (!@max.is_a?(Integer) || @max.negative?)
15
+ raise ArgumentError, "min #{@min} cannot be greater than max #{@max}" if !@min.nil? && !@max.nil? && @min > @max
16
+ end
17
+
18
+ def validate_param!(attr_name, params)
19
+ param = params[attr_name]
20
+
21
+ raise ArgumentError, "parameter #{param} does not support #length" unless param.respond_to?(:length)
22
+
23
+ return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max)
24
+
25
+ raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: build_message)
26
+ end
27
+
28
+ def build_message
29
+ if options_key?(:message)
30
+ @option[:message]
31
+ elsif @min && @max
32
+ format I18n.t(:length, scope: 'grape.errors.messages'), min: @min, max: @max
33
+ elsif @min
34
+ format I18n.t(:length_min, scope: 'grape.errors.messages'), min: @min
35
+ else
36
+ format I18n.t(:length_max, scope: 'grape.errors.messages'), max: @max
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -85,7 +85,12 @@ module Grape
85
85
  end
86
86
 
87
87
  def required_for_root_scope?
88
- @required && @scope.root?
88
+ return false unless @required
89
+
90
+ scope = @scope
91
+ scope = scope.parent while scope.lateral?
92
+
93
+ scope.root?
89
94
  end
90
95
 
91
96
  def validation_exception(attr_name, message)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grape
4
- # Registry to store and locate known Validators.
5
4
  module Validations
6
5
  module_function
7
6
 
@@ -12,7 +11,7 @@ module Grape
12
11
  # Register a new validator, so it can be used to validate parameters.
13
12
  # @param short_name [String] all lower-case, no spaces
14
13
  # @param klass [Class] the validator class. Should inherit from
15
- # Validations::Base.
14
+ # Grape::Validations::Validators::Base.
16
15
  def register_validator(short_name, klass)
17
16
  validators[short_name] = klass
18
17
  end
@@ -21,14 +20,11 @@ module Grape
21
20
  validators.delete(short_name)
22
21
  end
23
22
 
24
- # Find a validator and if not found will try to load it
25
23
  def require_validator(short_name)
26
24
  str_name = short_name.to_s
27
- validators.fetch(str_name) do
28
- Grape::Validations::Validators.const_get("#{str_name.camelize}Validator")
29
- end
25
+ validators.fetch(str_name) { Grape::Validations::Validators.const_get(:"#{str_name.camelize}Validator") }
30
26
  rescue NameError
31
- raise Grape::Exceptions::UnknownValidator.new(short_name)
27
+ raise Grape::Exceptions::UnknownValidator, short_name
32
28
  end
33
29
  end
34
30
  end
data/lib/grape/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Grape
4
4
  # The current version of Grape.
5
- VERSION = '2.0.0'
5
+ VERSION = '2.1.1'
6
6
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grape
4
- if Object.const_defined? :MultiXml
4
+ if defined?(::MultiXml)
5
5
  Xml = ::MultiXml
6
6
  else
7
7
  Xml = ::ActiveSupport::XmlMini