grape 2.0.0 → 2.4.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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +151 -1
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +404 -334
  5. data/UPGRADING.md +279 -7
  6. data/grape.gemspec +8 -8
  7. data/lib/grape/api/instance.rb +34 -66
  8. data/lib/grape/api.rb +47 -70
  9. data/lib/grape/content_types.rb +13 -10
  10. data/lib/grape/cookies.rb +31 -24
  11. data/lib/grape/dry_types.rb +0 -2
  12. data/lib/grape/dsl/api.rb +0 -2
  13. data/lib/grape/dsl/desc.rb +49 -44
  14. data/lib/grape/dsl/headers.rb +2 -2
  15. data/lib/grape/dsl/helpers.rb +8 -4
  16. data/lib/grape/dsl/inside_route.rb +67 -54
  17. data/lib/grape/dsl/parameters.rb +10 -9
  18. data/lib/grape/dsl/request_response.rb +14 -18
  19. data/lib/grape/dsl/routing.rb +34 -17
  20. data/lib/grape/dsl/validations.rb +13 -0
  21. data/lib/grape/endpoint.rb +120 -118
  22. data/lib/grape/{util/env.rb → env.rb} +0 -5
  23. data/lib/grape/error_formatter/base.rb +51 -21
  24. data/lib/grape/error_formatter/json.rb +7 -15
  25. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  26. data/lib/grape/error_formatter/txt.rb +11 -17
  27. data/lib/grape/error_formatter/xml.rb +3 -13
  28. data/lib/grape/error_formatter.rb +5 -25
  29. data/lib/grape/exceptions/base.rb +18 -30
  30. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  31. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  32. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  33. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  34. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  35. data/lib/grape/exceptions/validation.rb +5 -6
  36. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  37. data/lib/grape/exceptions/validation_errors.rb +4 -6
  38. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  39. data/lib/grape/extensions/hash.rb +7 -2
  40. data/lib/grape/extensions/hashie/mash.rb +3 -5
  41. data/lib/grape/formatter/base.rb +16 -0
  42. data/lib/grape/formatter/json.rb +4 -6
  43. data/lib/grape/formatter/serializable_hash.rb +1 -1
  44. data/lib/grape/formatter/txt.rb +3 -5
  45. data/lib/grape/formatter/xml.rb +4 -6
  46. data/lib/grape/formatter.rb +7 -25
  47. data/lib/grape/{util/json.rb → json.rb} +1 -3
  48. data/lib/grape/locale/en.yml +46 -42
  49. data/lib/grape/middleware/auth/base.rb +11 -34
  50. data/lib/grape/middleware/auth/dsl.rb +23 -31
  51. data/lib/grape/middleware/base.rb +41 -23
  52. data/lib/grape/middleware/error.rb +77 -76
  53. data/lib/grape/middleware/formatter.rb +48 -79
  54. data/lib/grape/middleware/globals.rb +1 -3
  55. data/lib/grape/middleware/stack.rb +26 -37
  56. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  57. data/lib/grape/middleware/versioner/base.rb +74 -0
  58. data/lib/grape/middleware/versioner/header.rb +59 -126
  59. data/lib/grape/middleware/versioner/param.rb +4 -25
  60. data/lib/grape/middleware/versioner/path.rb +10 -34
  61. data/lib/grape/middleware/versioner.rb +7 -14
  62. data/lib/grape/namespace.rb +4 -5
  63. data/lib/grape/params_builder/base.rb +18 -0
  64. data/lib/grape/params_builder/hash.rb +11 -0
  65. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  66. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  67. data/lib/grape/params_builder.rb +32 -0
  68. data/lib/grape/parser/base.rb +16 -0
  69. data/lib/grape/parser/json.rb +6 -8
  70. data/lib/grape/parser/xml.rb +6 -8
  71. data/lib/grape/parser.rb +5 -23
  72. data/lib/grape/path.rb +38 -60
  73. data/lib/grape/request.rb +161 -30
  74. data/lib/grape/router/base_route.rb +39 -0
  75. data/lib/grape/router/greedy_route.rb +20 -0
  76. data/lib/grape/router/pattern.rb +45 -31
  77. data/lib/grape/router/route.rb +28 -57
  78. data/lib/grape/router.rb +56 -43
  79. data/lib/grape/util/base_inheritable.rb +4 -4
  80. data/lib/grape/util/cache.rb +0 -3
  81. data/lib/grape/util/endpoint_configuration.rb +1 -1
  82. data/lib/grape/util/header.rb +13 -0
  83. data/lib/grape/util/inheritable_values.rb +0 -2
  84. data/lib/grape/util/lazy/block.rb +29 -0
  85. data/lib/grape/util/lazy/value.rb +38 -0
  86. data/lib/grape/util/lazy/value_array.rb +21 -0
  87. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  88. data/lib/grape/util/lazy/value_hash.rb +21 -0
  89. data/lib/grape/util/media_type.rb +70 -0
  90. data/lib/grape/util/registry.rb +27 -0
  91. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  92. data/lib/grape/util/stackable_values.rb +1 -6
  93. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  94. data/lib/grape/validations/attributes_doc.rb +38 -36
  95. data/lib/grape/validations/attributes_iterator.rb +1 -0
  96. data/lib/grape/validations/contract_scope.rb +34 -0
  97. data/lib/grape/validations/params_scope.rb +36 -32
  98. data/lib/grape/validations/types/array_coercer.rb +0 -2
  99. data/lib/grape/validations/types/dry_type_coercer.rb +9 -15
  100. data/lib/grape/validations/types/json.rb +0 -2
  101. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  102. data/lib/grape/validations/types/set_coercer.rb +0 -3
  103. data/lib/grape/validations/types.rb +0 -3
  104. data/lib/grape/validations/validator_factory.rb +2 -2
  105. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  106. data/lib/grape/validations/validators/base.rb +8 -11
  107. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  108. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  109. data/lib/grape/validations/validators/default_validator.rb +6 -2
  110. data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
  111. data/lib/grape/validations/validators/except_values_validator.rb +2 -2
  112. data/lib/grape/validations/validators/length_validator.rb +49 -0
  113. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  114. data/lib/grape/validations/validators/regexp_validator.rb +2 -2
  115. data/lib/grape/validations/validators/values_validator.rb +20 -57
  116. data/lib/grape/validations.rb +8 -21
  117. data/lib/grape/version.rb +1 -1
  118. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  119. data/lib/grape.rb +42 -274
  120. metadata +45 -44
  121. data/lib/grape/eager_load.rb +0 -20
  122. data/lib/grape/http/headers.rb +0 -71
  123. data/lib/grape/middleware/helpers.rb +0 -12
  124. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  125. data/lib/grape/router/attribute_translator.rb +0 -63
  126. data/lib/grape/util/lazy_block.rb +0 -27
  127. data/lib/grape/util/lazy_object.rb +0 -43
  128. data/lib/grape/util/lazy_value.rb +0 -91
  129. data/lib/grape/util/registrable.rb +0 -15
  130. data/lib/grape/validations/types/build_coercer.rb +0 -94
@@ -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
@@ -21,6 +21,7 @@ module Grape
21
21
  private
22
22
 
23
23
  def do_each(params_to_process, parent_indicies = [], &block)
24
+ @scope.reset_index # gets updated depending on the size of params_to_process
24
25
  params_to_process.each_with_index do |resource_params, index|
25
26
  # when we get arrays of arrays it means that target element located inside array
26
27
  # we need this because we want to know parent arrays indicies
@@ -0,0 +1,34 @@
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: Grape::Validations.require_validator(:contract_scope),
27
+ opts: { schema: contract, fail_fast: false }
28
+ }
29
+
30
+ api.namespace_stackable(:validations, validator_options)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,12 +1,10 @@
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
8
6
  attr_accessor :element, :parent, :index
9
- attr_reader :type
7
+ attr_reader :type, :params_meeting_dependency
10
8
 
11
9
  include Grape::DSL::Parameters
12
10
 
@@ -69,6 +67,7 @@ module Grape
69
67
  @type = opts[:type]
70
68
  @group = opts[:group]
71
69
  @dependent_on = opts[:dependent_on]
70
+ @params_meeting_dependency = []
72
71
  @declared_params = []
73
72
  @index = nil
74
73
 
@@ -95,17 +94,18 @@ module Grape
95
94
 
96
95
  def meets_dependency?(params, request_params)
97
96
  return true unless @dependent_on
98
-
99
97
  return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
100
98
 
101
- return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
99
+ if params.is_a?(Array)
100
+ @params_meeting_dependency = params.flatten.filter { |param| meets_dependency?(param, request_params) }
101
+ return @params_meeting_dependency.present?
102
+ end
102
103
 
103
104
  meets_hash_dependency?(params)
104
105
  end
105
106
 
106
107
  def attr_meets_dependency?(params)
107
108
  return true unless @dependent_on
108
-
109
109
  return false if @parent.present? && !@parent.attr_meets_dependency?(params)
110
110
 
111
111
  meets_hash_dependency?(params)
@@ -132,7 +132,7 @@ module Grape
132
132
  def full_name(name, index: nil)
133
133
  if nested?
134
134
  # Find our containing element's name, and append ours.
135
- "#{@parent.full_name(@element)}#{brackets(@index || index)}#{brackets(name)}"
135
+ "#{@parent.full_name(@element)}#{brackets(index || @index)}#{brackets(name)}"
136
136
  elsif lateral?
137
137
  # Find the name of the element as if it was at the same nesting level
138
138
  # as our parent. We need to forward our index upward to achieve this.
@@ -171,27 +171,33 @@ module Grape
171
171
  !@optional
172
172
  end
173
173
 
174
+ def reset_index
175
+ @index = nil
176
+ end
177
+
174
178
  protected
175
179
 
176
180
  # Adds a parameter declaration to our list of validations.
177
181
  # @param attrs [Array] (see Grape::DSL::Parameters#requires)
178
- def push_declared_params(attrs, **opts)
179
- opts = opts.merge(declared_params_scope: self) unless opts.key?(:declared_params_scope)
180
- if lateral?
181
- @parent.push_declared_params(attrs, **opts)
182
- else
183
- push_renamed_param(full_path + [attrs.first], opts[:as]) \
184
- if opts && opts[:as]
182
+ def push_declared_params(attrs, opts = {})
183
+ opts[:declared_params_scope] = self unless opts.key?(:declared_params_scope)
184
+ return @parent.push_declared_params(attrs, opts) if lateral?
185
185
 
186
- @declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
187
- end
186
+ push_renamed_param(full_path + [attrs.first], opts[:as]) if opts[:as]
187
+ @declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
188
188
  end
189
189
 
190
190
  # Get the full path of the parameter scope in the hierarchy.
191
191
  #
192
192
  # @return [Array<Symbol>] the nesting/path of the current parameter scope
193
193
  def full_path
194
- nested? ? @parent.full_path + [@element] : []
194
+ if nested?
195
+ (@parent.full_path + [@element])
196
+ elsif lateral?
197
+ @parent.full_path
198
+ else
199
+ []
200
+ end
195
201
  end
196
202
 
197
203
  private
@@ -211,11 +217,11 @@ module Grape
211
217
 
212
218
  def require_required_and_optional_fields(context, opts)
213
219
  if context == :all
214
- optional_fields = Array(opts[:except])
215
- required_fields = opts[:using].keys - optional_fields
220
+ optional_fields = Array.wrap(opts[:except])
221
+ required_fields = opts[:using].keys.delete_if { |f| optional_fields.include?(f) }
216
222
  else # context == :none
217
- required_fields = Array(opts[:except])
218
- optional_fields = opts[:using].keys - required_fields
223
+ required_fields = Array.wrap(opts[:except])
224
+ optional_fields = opts[:using].keys.delete_if { |f| required_fields.include?(f) }
219
225
  end
220
226
  required_fields.each do |field|
221
227
  field_opts = opts[:using][field]
@@ -231,7 +237,10 @@ module Grape
231
237
 
232
238
  def require_optional_fields(context, opts)
233
239
  optional_fields = opts[:using].keys
234
- optional_fields -= Array(opts[:except]) unless context == :all
240
+ unless context == :all
241
+ except_fields = Array.wrap(opts[:except])
242
+ optional_fields.delete_if { |f| except_fields.include?(f) }
243
+ end
235
244
  optional_fields.each do |field|
236
245
  field_opts = opts[:using][field]
237
246
  optional(field, field_opts) if field_opts
@@ -266,6 +275,7 @@ module Grape
266
275
  parent: self,
267
276
  optional: optional,
268
277
  type: type || Array,
278
+ group: @group,
269
279
  &block
270
280
  )
271
281
  end
@@ -295,12 +305,7 @@ module Grape
295
305
  # `optional` invocation that opened this scope.
296
306
  # @yield parameter scope
297
307
  def new_group_scope(attrs, &block)
298
- self.class.new(
299
- api: @api,
300
- parent: self,
301
- group: attrs.first,
302
- &block
303
- )
308
+ self.class.new(api: @api, parent: self, group: attrs.first, &block)
304
309
  end
305
310
 
306
311
  # Pushes declared params to parent or settings
@@ -463,8 +468,7 @@ module Grape
463
468
  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
469
 
465
470
  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
-
471
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
468
472
  end
469
473
 
470
474
  return unless excepts && !excepts.is_a?(Proc)
@@ -487,7 +491,7 @@ module Grape
487
491
  def validate_value_coercion(coerce_type, *values_list)
488
492
  return unless coerce_type
489
493
 
490
- coerce_type = coerce_type.first if coerce_type.is_a?(Array)
494
+ coerce_type = coerce_type.first if coerce_type.is_a?(Enumerable)
491
495
  values_list.each do |values|
492
496
  next if !values || values.is_a?(Proc)
493
497
 
@@ -526,7 +530,7 @@ module Grape
526
530
  def validates_presence(validations, attrs, doc, opts)
527
531
  return unless validations.key?(:presence) && validations[:presence]
528
532
 
529
- validate(:presence, validations.delete(:presence), attrs, doc, opts)
533
+ validate('presence', validations.delete(:presence), attrs, doc, opts)
530
534
  validations.delete(:message) if validations.key?(:message)
531
535
  end
532
536
  end
@@ -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,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,24 +22,20 @@ 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")
25
+ case type
26
+ when Array
27
+ ArrayCoercer
28
+ when Set
29
+ SetCoercer
30
+ else
31
+ raise ArgumentError, "Unknown type: #{type}"
29
32
  end
30
33
  end
31
34
 
32
35
  # Returns an instance of a coercer for a given type
33
36
  def coercer_instance_for(type, strict = false)
34
- return PrimitiveCoercer.new(type, strict) if type.instance_of?(Class)
35
-
36
- # in case of a collection (Array[Integer]) the type is an instance of a collection,
37
- # so we need to figure out the actual type
38
- collection_coercer_for(type.class).new(type, strict)
39
- end
40
-
41
- protected
42
-
43
- def collection_coercers
44
- @collection_coercers ||= {}
37
+ klass = type.instance_of?(Class) ? PrimitiveCoercer : collection_coercer_for(type)
38
+ klass.new(type, strict)
45
39
  end
46
40
  end
47
41
 
@@ -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
@@ -3,12 +3,12 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class ValidatorFactory
6
- def self.create_validator(**options)
6
+ def self.create_validator(options)
7
7
  options[:validator_class].new(options[:attributes],
8
8
  options[:options],
9
9
  options[:required],
10
10
  options[:params_scope],
11
- **options[:opts])
11
+ options[:opts])
12
12
  end
13
13
  end
14
14
  end
@@ -8,7 +8,7 @@ module Grape
8
8
  return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)
9
9
 
10
10
  value = params[attr_name]
11
- value = value.strip if value.respond_to?(:strip)
11
+ value = value.scrub if value.respond_to?(:scrub)
12
12
 
13
13
  return if value == false || value.present?
14
14
 
@@ -13,15 +13,14 @@ module Grape
13
13
  # @param options [Object] implementation-dependent Validator options
14
14
  # @param required [Boolean] attribute(s) are required or optional
15
15
  # @param scope [ParamsScope] parent scope for this Validator
16
- # @param opts [Array] additional validation options
17
- def initialize(attrs, options, required, scope, *opts)
16
+ # @param opts [Hash] additional validation options
17
+ def initialize(attrs, options, required, scope, opts)
18
18
  @attrs = Array(attrs)
19
19
  @option = options
20
20
  @required = required
21
21
  @scope = scope
22
- opts = opts.any? ? opts.shift : {}
23
- @fail_fast = opts.fetch(:fail_fast, false)
24
- @allow_blank = opts.fetch(:allow_blank, false)
22
+ @fail_fast = opts[:fail_fast]
23
+ @allow_blank = opts[:allow_blank]
25
24
  end
26
25
 
27
26
  # Validates a given request.
@@ -50,7 +49,7 @@ module Grape
50
49
  next if !@scope.required? && empty_val
51
50
  next unless @scope.meets_dependency?(val, params)
52
51
 
53
- validate_param!(attr_name, val) if @required || (val.respond_to?(:key?) && val.key?(attr_name))
52
+ validate_param!(attr_name, val) if @required || val.try(:key?, attr_name)
54
53
  rescue Grape::Exceptions::Validation => e
55
54
  array_errors << e
56
55
  end
@@ -59,10 +58,8 @@ module Grape
59
58
  end
60
59
 
61
60
  def self.inherited(klass)
62
- return if klass.name.blank?
63
-
64
- short_validator_name = klass.name.demodulize.underscore.delete_suffix('_validator')
65
- Validations.register_validator(short_validator_name, klass)
61
+ super
62
+ Validations.register(klass)
66
63
  end
67
64
 
68
65
  def message(default_key = nil)
@@ -72,7 +69,7 @@ module Grape
72
69
 
73
70
  def options_key?(key, options = nil)
74
71
  options = instance_variable_get(:@option) if options.nil?
75
- options.respond_to?(:key?) && options.key?(key) && !options[key].nil?
72
+ options.try(:key?, key) && !options[key].nil?
76
73
  end
77
74
 
78
75
  def fail_fast?
@@ -4,7 +4,7 @@ module Grape
4
4
  module Validations
5
5
  module Validators
6
6
  class CoerceValidator < Base
7
- def initialize(attrs, options, required, scope, **opts)
7
+ def initialize(attrs, options, required, scope, opts)
8
8
  super
9
9
 
10
10
  @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class ContractScopeValidator < Base
7
+ attr_reader :schema
8
+
9
+ def initialize(_attrs, _options, _required, _scope, opts)
10
+ super
11
+ @schema = opts.fetch(:schema)
12
+ end
13
+
14
+ # Validates a given request.
15
+ # @param request [Grape::Request] the request currently being handled
16
+ # @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed
17
+ # @return [void]
18
+ def validate(request)
19
+ res = schema.call(request.params)
20
+
21
+ if res.success?
22
+ request.params.deep_merge!(res.to_h)
23
+ return
24
+ end
25
+
26
+ raise Grape::Exceptions::ValidationArrayErrors.new(build_errors_from_messages(res.errors.messages))
27
+ end
28
+
29
+ private
30
+
31
+ def build_errors_from_messages(messages)
32
+ messages.map do |message|
33
+ full_name = message.path.first.to_s
34
+ full_name << "[#{message.path[1..].join('][')}]" if message.path.size > 1
35
+ Grape::Exceptions::Validation.new(params: [full_name], message: message.text)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -4,14 +4,18 @@ module Grape
4
4
  module Validations
5
5
  module Validators
6
6
  class DefaultValidator < Base
7
- def initialize(attrs, options, required, scope, **opts)
7
+ def initialize(attrs, options, required, scope, opts = {})
8
8
  @default = options
9
9
  super
10
10
  end
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
@@ -7,7 +7,7 @@ module Grape
7
7
  def validate_params!(params)
8
8
  keys = keys_in_common(params)
9
9
  return if keys.length == 1
10
- raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero?
10
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.empty?
11
11
 
12
12
  raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
13
13
  end
@@ -4,13 +4,13 @@ module Grape
4
4
  module Validations
5
5
  module Validators
6
6
  class ExceptValuesValidator < Base
7
- def initialize(attrs, options, required, scope, **opts)
7
+ def initialize(attrs, options, required, scope, opts)
8
8
  @except = options.is_a?(Hash) ? options[:value] : options
9
9
  super
10
10
  end
11
11
 
12
12
  def validate_param!(attr_name, params)
13
- return unless params.respond_to?(:key?) && params.key?(attr_name)
13
+ return unless params.try(:key?, attr_name)
14
14
 
15
15
  excepts = @except.is_a?(Proc) ? @except.call : @except
16
16
  return if excepts.nil?
@@ -0,0 +1,49 @@
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
+ @is = options[:is]
11
+
12
+ super
13
+
14
+ raise ArgumentError, 'min must be an integer greater than or equal to zero' if !@min.nil? && (!@min.is_a?(Integer) || @min.negative?)
15
+ raise ArgumentError, 'max must be an integer greater than or equal to zero' if !@max.nil? && (!@max.is_a?(Integer) || @max.negative?)
16
+ raise ArgumentError, "min #{@min} cannot be greater than max #{@max}" if !@min.nil? && !@max.nil? && @min > @max
17
+
18
+ return if @is.nil?
19
+ raise ArgumentError, 'is must be an integer greater than zero' if !@is.is_a?(Integer) || !@is.positive?
20
+ raise ArgumentError, 'is cannot be combined with min or max' if !@min.nil? || !@max.nil?
21
+ end
22
+
23
+ def validate_param!(attr_name, params)
24
+ param = params[attr_name]
25
+
26
+ return unless param.respond_to?(:length)
27
+
28
+ return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max) || (!@is.nil? && param.length != @is)
29
+
30
+ raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: build_message)
31
+ end
32
+
33
+ def build_message
34
+ if options_key?(:message)
35
+ @option[:message]
36
+ elsif @min && @max
37
+ format I18n.t(:length, scope: 'grape.errors.messages'), min: @min, max: @max
38
+ elsif @min
39
+ format I18n.t(:length_min, scope: 'grape.errors.messages'), min: @min
40
+ elsif @max
41
+ format I18n.t(:length_max, scope: 'grape.errors.messages'), max: @max
42
+ else
43
+ format I18n.t(:length_is, scope: 'grape.errors.messages'), is: @is
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -5,7 +5,7 @@ module Grape
5
5
  module Validators
6
6
  class PresenceValidator < Base
7
7
  def validate_param!(attr_name, params)
8
- return if params.respond_to?(:key?) && params.key?(attr_name)
8
+ return if params.try(:key?, attr_name)
9
9
 
10
10
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:presence))
11
11
  end
@@ -5,8 +5,8 @@ module Grape
5
5
  module Validators
6
6
  class RegexpValidator < Base
7
7
  def validate_param!(attr_name, params)
8
- return unless params.respond_to?(:key?) && params.key?(attr_name)
9
- return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) }
8
+ return unless params.try(:key?, attr_name)
9
+ return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.scrub.match?((options_key?(:value) ? @option[:value] : @option)) }
10
10
 
11
11
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))
12
12
  end