grape 1.5.1 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -3
  3. data/CONTRIBUTING.md +1 -0
  4. data/README.md +50 -8
  5. data/UPGRADING.md +69 -0
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +13 -17
  8. data/lib/grape/api.rb +19 -14
  9. data/lib/grape/cookies.rb +2 -0
  10. data/lib/grape/dry_types.rb +12 -0
  11. data/lib/grape/dsl/callbacks.rb +1 -1
  12. data/lib/grape/dsl/desc.rb +3 -5
  13. data/lib/grape/dsl/headers.rb +5 -2
  14. data/lib/grape/dsl/helpers.rb +7 -5
  15. data/lib/grape/dsl/inside_route.rb +18 -9
  16. data/lib/grape/dsl/middleware.rb +4 -4
  17. data/lib/grape/dsl/parameters.rb +4 -4
  18. data/lib/grape/dsl/request_response.rb +9 -6
  19. data/lib/grape/dsl/routing.rb +7 -6
  20. data/lib/grape/dsl/settings.rb +5 -5
  21. data/lib/grape/endpoint.rb +21 -36
  22. data/lib/grape/error_formatter/json.rb +2 -6
  23. data/lib/grape/error_formatter/xml.rb +2 -6
  24. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  25. data/lib/grape/exceptions/validation.rb +2 -3
  26. data/lib/grape/exceptions/validation_errors.rb +1 -1
  27. data/lib/grape/formatter/json.rb +1 -0
  28. data/lib/grape/formatter/serializable_hash.rb +2 -1
  29. data/lib/grape/formatter/xml.rb +1 -0
  30. data/lib/grape/locale/en.yml +1 -1
  31. data/lib/grape/middleware/auth/base.rb +3 -3
  32. data/lib/grape/middleware/auth/dsl.rb +7 -1
  33. data/lib/grape/middleware/base.rb +5 -3
  34. data/lib/grape/middleware/error.rb +1 -1
  35. data/lib/grape/middleware/formatter.rb +4 -4
  36. data/lib/grape/middleware/stack.rb +14 -20
  37. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  38. data/lib/grape/middleware/versioner/header.rb +6 -4
  39. data/lib/grape/middleware/versioner/param.rb +1 -0
  40. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  41. data/lib/grape/middleware/versioner/path.rb +2 -0
  42. data/lib/grape/parser/json.rb +1 -1
  43. data/lib/grape/parser/xml.rb +1 -1
  44. data/lib/grape/path.rb +1 -0
  45. data/lib/grape/request.rb +3 -0
  46. data/lib/grape/router/pattern.rb +1 -1
  47. data/lib/grape/router/route.rb +2 -2
  48. data/lib/grape/router.rb +6 -0
  49. data/lib/grape/util/inheritable_setting.rb +1 -3
  50. data/lib/grape/util/json.rb +2 -0
  51. data/lib/grape/util/lazy_value.rb +3 -2
  52. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  53. data/lib/grape/validations/params_scope.rb +89 -56
  54. data/lib/grape/validations/types/array_coercer.rb +0 -2
  55. data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
  56. data/lib/grape/validations/types/dry_type_coercer.rb +2 -11
  57. data/lib/grape/validations/types/invalid_value.rb +24 -0
  58. data/lib/grape/validations/types/json.rb +2 -3
  59. data/lib/grape/validations/types/primitive_coercer.rb +5 -7
  60. data/lib/grape/validations/types/set_coercer.rb +0 -3
  61. data/lib/grape/validations/types.rb +83 -12
  62. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  63. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  64. data/lib/grape/validations/validators/as_validator.rb +14 -0
  65. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  66. data/lib/grape/validations/validators/base.rb +75 -69
  67. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  68. data/lib/grape/validations/validators/default_validator.rb +51 -0
  69. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  70. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  71. data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
  72. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  73. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  74. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  75. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  76. data/lib/grape/validations/validators/values_validator.rb +88 -0
  77. data/lib/grape/version.rb +1 -1
  78. data/lib/grape.rb +61 -24
  79. data/spec/grape/api/custom_validations_spec.rb +77 -45
  80. data/spec/grape/api/deeply_included_options_spec.rb +3 -3
  81. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -1
  82. data/spec/grape/api/invalid_format_spec.rb +2 -0
  83. data/spec/grape/api/recognize_path_spec.rb +1 -1
  84. data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
  85. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -15
  86. data/spec/grape/api_remount_spec.rb +25 -19
  87. data/spec/grape/api_spec.rb +510 -220
  88. data/spec/grape/dsl/callbacks_spec.rb +2 -1
  89. data/spec/grape/dsl/headers_spec.rb +39 -9
  90. data/spec/grape/dsl/helpers_spec.rb +3 -2
  91. data/spec/grape/dsl/inside_route_spec.rb +6 -4
  92. data/spec/grape/dsl/logger_spec.rb +16 -18
  93. data/spec/grape/dsl/middleware_spec.rb +2 -1
  94. data/spec/grape/dsl/parameters_spec.rb +2 -0
  95. data/spec/grape/dsl/request_response_spec.rb +1 -0
  96. data/spec/grape/dsl/routing_spec.rb +10 -7
  97. data/spec/grape/endpoint/declared_spec.rb +259 -12
  98. data/spec/grape/endpoint_spec.rb +77 -55
  99. data/spec/grape/entity_spec.rb +23 -23
  100. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -0
  101. data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -22
  102. data/spec/grape/exceptions/validation_errors_spec.rb +13 -10
  103. data/spec/grape/exceptions/validation_spec.rb +5 -3
  104. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -7
  105. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -8
  106. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -8
  107. data/spec/grape/integration/rack_sendfile_spec.rb +1 -1
  108. data/spec/grape/loading_spec.rb +8 -8
  109. data/spec/grape/middleware/auth/dsl_spec.rb +15 -6
  110. data/spec/grape/middleware/auth/strategies_spec.rb +60 -20
  111. data/spec/grape/middleware/base_spec.rb +24 -15
  112. data/spec/grape/middleware/error_spec.rb +2 -2
  113. data/spec/grape/middleware/exception_spec.rb +111 -161
  114. data/spec/grape/middleware/formatter_spec.rb +27 -6
  115. data/spec/grape/middleware/globals_spec.rb +7 -4
  116. data/spec/grape/middleware/stack_spec.rb +15 -14
  117. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -1
  118. data/spec/grape/middleware/versioner/header_spec.rb +14 -13
  119. data/spec/grape/middleware/versioner/param_spec.rb +7 -1
  120. data/spec/grape/middleware/versioner/path_spec.rb +5 -1
  121. data/spec/grape/middleware/versioner_spec.rb +1 -1
  122. data/spec/grape/parser_spec.rb +4 -0
  123. data/spec/grape/path_spec.rb +52 -52
  124. data/spec/grape/presenters/presenter_spec.rb +7 -6
  125. data/spec/grape/request_spec.rb +6 -4
  126. data/spec/grape/util/inheritable_setting_spec.rb +7 -7
  127. data/spec/grape/util/inheritable_values_spec.rb +3 -2
  128. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -1
  129. data/spec/grape/util/stackable_values_spec.rb +7 -5
  130. data/spec/grape/validations/instance_behaivour_spec.rb +9 -10
  131. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -0
  132. data/spec/grape/validations/params_scope_spec.rb +46 -10
  133. data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -1
  134. data/spec/grape/validations/types/primitive_coercer_spec.rb +4 -4
  135. data/spec/grape/validations/types_spec.rb +8 -8
  136. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -56
  137. data/spec/grape/validations/validators/allow_blank_spec.rb +136 -140
  138. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -56
  139. data/spec/grape/validations/validators/coerce_spec.rb +139 -34
  140. data/spec/grape/validations/validators/default_spec.rb +72 -78
  141. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
  142. data/spec/grape/validations/validators/except_values_spec.rb +3 -3
  143. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -77
  144. data/spec/grape/validations/validators/presence_spec.rb +16 -1
  145. data/spec/grape/validations/validators/regexp_spec.rb +25 -31
  146. data/spec/grape/validations/validators/same_as_spec.rb +14 -20
  147. data/spec/grape/validations/validators/values_spec.rb +183 -178
  148. data/spec/grape/validations_spec.rb +99 -58
  149. data/spec/integration/eager_load/eager_load_spec.rb +2 -2
  150. data/spec/integration/multi_json/json_spec.rb +1 -1
  151. data/spec/integration/multi_xml/xml_spec.rb +1 -1
  152. data/spec/shared/versioning_examples.rb +32 -29
  153. data/spec/spec_helper.rb +12 -2
  154. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  155. data/spec/support/versioned_helpers.rb +1 -1
  156. metadata +117 -115
  157. data/lib/grape/validations/types/build_coercer.rb +0 -94
  158. data/lib/grape/validations/validators/all_or_none.rb +0 -15
  159. data/lib/grape/validations/validators/allow_blank.rb +0 -18
  160. data/lib/grape/validations/validators/as.rb +0 -16
  161. data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
  162. data/lib/grape/validations/validators/coerce.rb +0 -88
  163. data/lib/grape/validations/validators/default.rb +0 -48
  164. data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
  165. data/lib/grape/validations/validators/except_values.rb +0 -22
  166. data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
  167. data/lib/grape/validations/validators/presence.rb +0 -12
  168. data/lib/grape/validations/validators/regexp.rb +0 -13
  169. data/lib/grape/validations/validators/same_as.rb +0 -26
  170. data/lib/grape/validations/validators/values.rb +0 -83
@@ -13,6 +13,8 @@ module Grape
13
13
  # @param opts [Hash] options for this scope
14
14
  # @option opts :element [Symbol] the element that contains this scope; for
15
15
  # this to be relevant, @parent must be set
16
+ # @option opts :element_renamed [Symbol, nil] whenever this scope should
17
+ # be renamed and to what, given +nil+ no renaming is done
16
18
  # @option opts :parent [ParamsScope] the scope containing this scope
17
19
  # @option opts :api [API] the API endpoint to modify
18
20
  # @option opts :optional [Boolean] whether or not this scope needs to have
@@ -23,23 +25,24 @@ module Grape
23
25
  # validate if this param is present in the parent scope
24
26
  # @yield the instance context, open for parameter definitions
25
27
  def initialize(opts, &block)
26
- @element = opts[:element]
27
- @parent = opts[:parent]
28
- @api = opts[:api]
29
- @optional = opts[:optional] || false
30
- @type = opts[:type]
31
- @group = opts[:group] || {}
32
- @dependent_on = opts[:dependent_on]
28
+ @element = opts[:element]
29
+ @element_renamed = opts[:element_renamed]
30
+ @parent = opts[:parent]
31
+ @api = opts[:api]
32
+ @optional = opts[:optional] || false
33
+ @type = opts[:type]
34
+ @group = opts[:group] || {}
35
+ @dependent_on = opts[:dependent_on]
33
36
  @declared_params = []
34
37
  @index = nil
35
38
 
36
- instance_eval(&block) if block_given?
39
+ instance_eval(&block) if block
37
40
 
38
41
  configure_declared_params
39
42
  end
40
43
 
41
44
  def configuration
42
- @api.configuration.evaluate
45
+ @api.configuration.respond_to?(:evaluate) ? @api.configuration.evaluate : @api.configuration
43
46
  end
44
47
 
45
48
  # @return [Boolean] whether or not this entire scope needs to be
@@ -50,15 +53,14 @@ module Grape
50
53
  return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
51
54
  return false unless meets_dependency?(scoped_params, parameters)
52
55
  return true if parent.nil?
56
+
53
57
  parent.should_validate?(parameters)
54
58
  end
55
59
 
56
60
  def meets_dependency?(params, request_params)
57
61
  return true unless @dependent_on
58
62
 
59
- if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
60
- return false
61
- end
63
+ return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
62
64
 
63
65
  return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
64
66
 
@@ -129,18 +131,35 @@ module Grape
129
131
  if lateral?
130
132
  @parent.push_declared_params(attrs, **opts)
131
133
  else
132
- if opts && opts[:as]
133
- @api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || [])
134
- @api.route_setting(:renamed_params) << { attrs.first => opts[:as] }
135
- attrs = [opts[:as]]
136
- end
134
+ push_renamed_param(full_path + [attrs.first], opts[:as]) \
135
+ if opts && opts[:as]
137
136
 
138
137
  @declared_params.concat attrs
139
138
  end
140
139
  end
141
140
 
141
+ # Get the full path of the parameter scope in the hierarchy.
142
+ #
143
+ # @return [Array<Symbol>] the nesting/path of the current parameter scope
144
+ def full_path
145
+ nested? ? @parent.full_path + [@element] : []
146
+ end
147
+
142
148
  private
143
149
 
150
+ # Add a new parameter which should be renamed when using the +#declared+
151
+ # method.
152
+ #
153
+ # @param path [Array<String, Symbol>] the full path of the parameter
154
+ # (including the parameter name as last array element)
155
+ # @param new_name [String, Symbol] the new name of the parameter (the
156
+ # renamed name, with the +as: ...+ semantic)
157
+ def push_renamed_param(path, new_name)
158
+ base = @api.route_setting(:renamed_params) || {}
159
+ base[Array(path).map(&:to_s)] = new_name.to_s
160
+ @api.route_setting(:renamed_params, base)
161
+ end
162
+
144
163
  def require_required_and_optional_fields(context, opts)
145
164
  if context == :all
146
165
  optional_fields = Array(opts[:except])
@@ -152,6 +171,7 @@ module Grape
152
171
  required_fields.each do |field|
153
172
  field_opts = opts[:using][field]
154
173
  raise ArgumentError, "required field not exist: #{field}" unless field_opts
174
+
155
175
  requires(field, field_opts)
156
176
  end
157
177
  optional_fields.each do |field|
@@ -191,11 +211,12 @@ module Grape
191
211
  end
192
212
 
193
213
  self.class.new(
194
- api: @api,
195
- element: attrs[1][:as] || attrs.first,
196
- parent: self,
214
+ api: @api,
215
+ element: attrs.first,
216
+ element_renamed: attrs[1][:as],
217
+ parent: self,
197
218
  optional: optional,
198
- type: type || Array,
219
+ type: type || Array,
199
220
  &block
200
221
  )
201
222
  end
@@ -209,11 +230,11 @@ module Grape
209
230
  # @yield parameter scope
210
231
  def new_lateral_scope(options, &block)
211
232
  self.class.new(
212
- api: @api,
213
- element: nil,
214
- parent: self,
215
- options: @optional,
216
- type: type == Array ? Array : Hash,
233
+ api: @api,
234
+ element: nil,
235
+ parent: self,
236
+ options: @optional,
237
+ type: type == Array ? Array : Hash,
217
238
  dependent_on: options[:dependent_on],
218
239
  &block
219
240
  )
@@ -226,15 +247,17 @@ module Grape
226
247
  # @yield parameter scope
227
248
  def new_group_scope(attrs, &block)
228
249
  self.class.new(
229
- api: @api,
230
- parent: self,
231
- group: attrs.first,
250
+ api: @api,
251
+ parent: self,
252
+ group: attrs.first,
232
253
  &block
233
254
  )
234
255
  end
235
256
 
236
257
  # Pushes declared params to parent or settings
237
258
  def configure_declared_params
259
+ push_renamed_param(full_path, @element_renamed) if @element_renamed
260
+
238
261
  if nested?
239
262
  @parent.push_declared_params [element => @declared_params]
240
263
  else
@@ -283,16 +306,15 @@ module Grape
283
306
 
284
307
  doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
285
308
 
286
- full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
287
- @api.document_attribute(full_attrs, doc_attrs)
309
+ document_attribute(attrs, doc_attrs)
288
310
 
289
311
  opts = derive_validator_options(validations)
290
312
 
313
+ order_specific_validations = Set[:as]
314
+
291
315
  # Validate for presence before any other validators
292
- if validations.key?(:presence) && validations[:presence]
293
- validate('presence', validations[:presence], attrs, doc_attrs, opts)
294
- validations.delete(:presence)
295
- validations.delete(:message) if validations.key?(:message)
316
+ validates_presence(validations, attrs, doc_attrs, opts) do |validation_type|
317
+ order_specific_validations << validation_type
296
318
  end
297
319
 
298
320
  # Before we run the rest of the validators, let's handle
@@ -301,6 +323,8 @@ module Grape
301
323
  coerce_type validations, attrs, doc_attrs, opts
302
324
 
303
325
  validations.each do |type, options|
326
+ next if order_specific_validations.include?(type)
327
+
304
328
  validate(type, options, attrs, doc_attrs, opts)
305
329
  end
306
330
  end
@@ -319,9 +343,7 @@ module Grape
319
343
  # @return [class-like] type to which the parameter will be coerced
320
344
  # @raise [ArgumentError] if the given type options are invalid
321
345
  def infer_coercion(validations)
322
- if validations.key?(:type) && validations.key?(:types)
323
- raise ArgumentError, ':type may not be supplied with :types'
324
- end
346
+ raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types)
325
347
 
326
348
  validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)
327
349
  validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)
@@ -357,6 +379,7 @@ module Grape
357
379
  # but not special JSON types, which
358
380
  # already imply coercion method
359
381
  return unless [JSON, Array[JSON]].include? validations[:coerce]
382
+
360
383
  raise ArgumentError, 'coerce_with disallowed for type: JSON'
361
384
  end
362
385
 
@@ -384,6 +407,7 @@ module Grape
384
407
 
385
408
  def guess_coerce_type(coerce_type, *values_list)
386
409
  return coerce_type unless coerce_type == Array
410
+
387
411
  values_list.each do |values|
388
412
  next if !values || values.is_a?(Proc)
389
413
  return values.first.class if values.is_a?(Range) || !values.empty?
@@ -394,14 +418,11 @@ module Grape
394
418
  def check_incompatible_option_values(default, values, except_values, excepts)
395
419
  return unless default && !default.is_a?(Proc)
396
420
 
397
- if values && !values.is_a?(Proc)
398
- raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) \
399
- unless Array(default).all? { |def_val| values.include?(def_val) }
400
- end
421
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) }
401
422
 
402
- if except_values && !except_values.is_a?(Proc)
423
+ if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
403
424
  raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
404
- unless Array(default).none? { |def_val| except_values.include?(def_val) }
425
+
405
426
  end
406
427
 
407
428
  return unless excepts && !excepts.is_a?(Proc)
@@ -415,11 +436,11 @@ module Grape
415
436
  raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
416
437
 
417
438
  validator_options = {
418
- attributes: attrs,
419
- options: options,
420
- required: doc_attrs[:required],
421
- params_scope: self,
422
- opts: opts,
439
+ attributes: attrs,
440
+ options: options,
441
+ required: doc_attrs[:required],
442
+ params_scope: self,
443
+ opts: opts,
423
444
  validator_class: validator_class
424
445
  }
425
446
  @api.namespace_stackable(:validations, validator_options)
@@ -427,21 +448,20 @@ module Grape
427
448
 
428
449
  def validate_value_coercion(coerce_type, *values_list)
429
450
  return unless coerce_type
451
+
430
452
  coerce_type = coerce_type.first if coerce_type.is_a?(Array)
431
453
  values_list.each do |values|
432
454
  next if !values || values.is_a?(Proc)
455
+
433
456
  value_types = values.is_a?(Range) ? [values.begin, values.end] : values
434
- if coerce_type == Grape::API::Boolean
435
- value_types = value_types.map { |type| Grape::API::Boolean.build(type) }
436
- end
437
- unless value_types.all? { |v| v.is_a? coerce_type }
438
- raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
439
- end
457
+ value_types = value_types.map { |type| Grape::API::Boolean.build(type) } if coerce_type == Grape::API::Boolean
458
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) unless value_types.all?(coerce_type)
440
459
  end
441
460
  end
442
461
 
443
462
  def extract_message_option(attrs)
444
463
  return nil unless attrs.is_a?(Array)
464
+
445
465
  opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
446
466
  opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil
447
467
  end
@@ -461,9 +481,22 @@ module Grape
461
481
 
462
482
  {
463
483
  allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
464
- fail_fast: validations.delete(:fail_fast) || false
484
+ fail_fast: validations.delete(:fail_fast) || false
465
485
  }
466
486
  end
487
+
488
+ def validates_presence(validations, attrs, doc_attrs, opts)
489
+ return unless validations.key?(:presence) && validations[:presence]
490
+
491
+ validate(:presence, validations[:presence], attrs, doc_attrs, opts)
492
+ yield :presence
493
+ yield :message if validations.key?(:message)
494
+ end
495
+
496
+ def document_attribute(attrs, doc_attrs)
497
+ full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
498
+ @api.document_attribute(full_attrs, doc_attrs)
499
+ end
467
500
  end
468
501
  end
469
502
  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
@@ -52,10 +52,11 @@ module Grape
52
52
  # this should always be a string.
53
53
  # @return [Object] the coerced result
54
54
  def call(val)
55
- return if val.nil?
56
-
57
55
  coerced_val = @method.call(val)
56
+
57
+ return coerced_val if coerced_val.is_a?(InvalidValue)
58
58
  return InvalidValue.new unless coerced?(coerced_val)
59
+
59
60
  coerced_val
60
61
  end
61
62
 
@@ -1,14 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-types'
4
-
5
- module DryTypes
6
- # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
7
- # a container in this case. Check documentation for more information
8
- # https://dry-rb.org/gems/dry-types/1.2/getting-started/
9
- include Dry.Types()
10
- end
11
-
12
3
  module Grape
13
4
  module Validations
14
5
  module Types
@@ -35,7 +26,7 @@ module Grape
35
26
 
36
27
  # Returns an instance of a coercer for a given type
37
28
  def coercer_instance_for(type, strict = false)
38
- return PrimitiveCoercer.new(type, strict) if type.class == Class
29
+ return PrimitiveCoercer.new(type, strict) if type.instance_of?(Class)
39
30
 
40
31
  # in case of a collection (Array[Integer]) the type is an instance of a collection,
41
32
  # so we need to figure out the actual type
@@ -52,7 +43,7 @@ module Grape
52
43
  def initialize(type, strict = false)
53
44
  @type = type
54
45
  @strict = strict
55
- @scope = strict ? DryTypes::Strict : DryTypes::Params
46
+ @scope = strict ? Grape::DryTypes::Strict : Grape::DryTypes::Params
56
47
  end
57
48
 
58
49
  # Coerces the given value to a type which was specified during
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Types
6
+ # Instances of this class may be used as tokens to denote that a parameter value could not be
7
+ # coerced. The given message will be used as a validation error.
8
+ class InvalidValue
9
+ attr_reader :message
10
+
11
+ def initialize(message = nil)
12
+ @message = message
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # only exists to make it shorter for external use
20
+ module Grape
21
+ module Types
22
+ InvalidValue = Class.new(Grape::Validations::Types::InvalidValue)
23
+ end
24
+ end
@@ -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
@@ -22,6 +20,7 @@ module Grape
22
20
 
23
21
  # Allow nulls and blank strings
24
22
  return if input.nil? || input.match?(/^\s*$/)
23
+
25
24
  JSON.parse(input, symbolize_names: true)
26
25
  end
27
26
 
@@ -41,7 +40,7 @@ module Grape
41
40
  # @param value [Object] result of {#parse}
42
41
  # @return [true,false]
43
42
  def coerced_collection?(value)
44
- value.is_a?(::Array) && value.all? { |i| i.is_a? ::Hash }
43
+ value.is_a?(::Array) && value.all?(::Hash)
45
44
  end
46
45
  end
47
46
  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
@@ -10,16 +8,16 @@ module Grape
10
8
  # that it has the proper type.
11
9
  class PrimitiveCoercer < DryTypeCoercer
12
10
  MAPPING = {
13
- Grape::API::Boolean => DryTypes::Params::Bool,
14
- BigDecimal => DryTypes::Params::Decimal,
11
+ Grape::API::Boolean => Grape::DryTypes::Params::Bool,
12
+ BigDecimal => Grape::DryTypes::Params::Decimal,
15
13
 
16
14
  # unfortunately, a +Params+ scope doesn't contain String
17
- String => DryTypes::Coercible::String
15
+ String => Grape::DryTypes::Coercible::String
18
16
  }.freeze
19
17
 
20
18
  STRICT_MAPPING = {
21
- Grape::API::Boolean => DryTypes::Strict::Bool,
22
- BigDecimal => DryTypes::Strict::Decimal
19
+ Grape::API::Boolean => Grape::DryTypes::Strict::Bool,
20
+ BigDecimal => Grape::DryTypes::Strict::Decimal
23
21
  }.freeze
24
22
 
25
23
  def initialize(type, strict = false)
@@ -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,13 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'types/build_coercer'
4
- require_relative 'types/custom_type_coercer'
5
- require_relative 'types/custom_type_collection_coercer'
6
- require_relative 'types/multiple_type_coercer'
7
- require_relative 'types/variant_collection_coercer'
8
- require_relative 'types/json'
9
- require_relative 'types/file'
10
-
11
3
  module Grape
12
4
  module Validations
13
5
  # Module for code related to grape's system for
@@ -21,10 +13,6 @@ module Grape
21
13
  # and {Grape::Dsl::Parameters#optional}. The main
22
14
  # entry point for this process is {Types.build_coercer}.
23
15
  module Types
24
- # Instances of this class may be used as tokens to denote that
25
- # a parameter value could not be coerced.
26
- class InvalidValue; end
27
-
28
16
  # Types representing a single value, which are coerced.
29
17
  PRIMITIVES = [
30
18
  # Numerical
@@ -146,6 +134,89 @@ module Grape
146
134
  def self.map_special(type)
147
135
  SPECIAL.fetch(type, type)
148
136
  end
137
+
138
+ # Chooses the best coercer for the given type. For example, if the type
139
+ # is Integer, it will return a coercer which will be able to coerce a value
140
+ # to the integer.
141
+ #
142
+ # There are a few very special coercers which might be returned.
143
+ #
144
+ # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
145
+ # the given type implies values in an array with different types.
146
+ # For example, +[Integer, String]+ allows integer and string values in
147
+ # an array.
148
+ #
149
+ # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
150
+ # a method is specified by a user with +coerce_with+ option or the user
151
+ # specifies a custom type which implements requirments of
152
+ # +Grape::Types::CustomTypeCoercer+.
153
+ #
154
+ # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
155
+ # previous one, but it expects an array or set of values having a custom
156
+ # type implemented by the user.
157
+ #
158
+ # There is also a group of custom types implemented by Grape, check
159
+ # +Grape::Validations::Types::SPECIAL+ to get the full list.
160
+ #
161
+ # @param type [Class] the type to which input strings
162
+ # should be coerced
163
+ # @param method [Class,#call] the coercion method to use
164
+ # @return [Object] object to be used
165
+ # for coercion and type validation
166
+ def self.build_coercer(type, method: nil, strict: false)
167
+ cache_instance(type, method, strict) do
168
+ create_coercer_instance(type, method, strict)
169
+ end
170
+ end
171
+
172
+ def self.create_coercer_instance(type, method, strict)
173
+ # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
174
+ type = Types.map_special(type)
175
+
176
+ # Use a special coercer for multiply-typed parameters.
177
+ if Types.multiple?(type)
178
+ MultipleTypeCoercer.new(type, method)
179
+
180
+ # Use a special coercer for custom types and coercion methods.
181
+ elsif method || Types.custom?(type)
182
+ CustomTypeCoercer.new(type, method)
183
+
184
+ # Special coercer for collections of types that implement a parse method.
185
+ # CustomTypeCoercer (above) already handles such types when an explicit coercion
186
+ # method is supplied.
187
+ elsif Types.collection_of_custom?(type)
188
+ Types::CustomTypeCollectionCoercer.new(
189
+ Types.map_special(type.first), type.is_a?(Set)
190
+ )
191
+ else
192
+ DryTypeCoercer.coercer_instance_for(type, strict)
193
+ end
194
+ end
195
+
196
+ def self.cache_instance(type, method, strict, &_block)
197
+ key = cache_key(type, method, strict)
198
+
199
+ return @__cache[key] if @__cache.key?(key)
200
+
201
+ instance = yield
202
+
203
+ @__cache_write_lock.synchronize do
204
+ @__cache[key] = instance
205
+ end
206
+
207
+ instance
208
+ end
209
+
210
+ def self.cache_key(type, method, strict)
211
+ [type, method, strict].each_with_object(+'_') do |val, memo|
212
+ next if val.nil?
213
+
214
+ memo << '_' << val.to_s
215
+ end
216
+ end
217
+
218
+ instance_variable_set(:@__cache, {})
219
+ instance_variable_set(:@__cache_write_lock, Mutex.new)
149
220
  end
150
221
  end
151
222
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AllOrNoneOfValidator < MultipleParamsBase
7
+ def validate_params!(params)
8
+ keys = keys_in_common(params)
9
+ return if keys.empty? || keys.length == all_keys.length
10
+
11
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none))
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AllowBlankValidator < Base
7
+ def validate_param!(attr_name, params)
8
+ return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)
9
+
10
+ value = params[attr_name]
11
+ value = value.strip if value.respond_to?(:strip)
12
+
13
+ return if value == false || value.present?
14
+
15
+ raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:blank))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AsValidator < Base
7
+ # We use a validator for renaming parameters. This is just a marker for
8
+ # the parameter scope to handle the renaming. No actual validation
9
+ # happens here.
10
+ def validate_param!(*); end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AtLeastOneOfValidator < MultipleParamsBase
7
+ def validate_params!(params)
8
+ return unless keys_in_common(params).empty?
9
+
10
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one))
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end