grape 2.0.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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