grape 1.5.1 → 1.6.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 (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