grape 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -66
  3. data/.rubocop_todo.yml +78 -17
  4. data/.travis.yml +7 -3
  5. data/Appraisals +7 -0
  6. data/CHANGELOG.md +24 -0
  7. data/CONTRIBUTING.md +7 -0
  8. data/Gemfile +1 -7
  9. data/Guardfile +1 -1
  10. data/README.md +560 -94
  11. data/RELEASING.md +1 -1
  12. data/Rakefile +10 -11
  13. data/UPGRADING.md +211 -3
  14. data/gemfiles/rails_3.gemfile +14 -0
  15. data/gemfiles/rails_4.gemfile +14 -0
  16. data/grape.gemspec +10 -9
  17. data/lib/backports/active_support/deep_dup.rb +49 -0
  18. data/lib/backports/active_support/duplicable.rb +88 -0
  19. data/lib/grape.rb +29 -2
  20. data/lib/grape/api.rb +59 -65
  21. data/lib/grape/dsl/api.rb +19 -0
  22. data/lib/grape/dsl/callbacks.rb +6 -4
  23. data/lib/grape/dsl/configuration.rb +49 -5
  24. data/lib/grape/dsl/helpers.rb +7 -8
  25. data/lib/grape/dsl/inside_route.rb +22 -10
  26. data/lib/grape/dsl/middleware.rb +5 -5
  27. data/lib/grape/dsl/parameters.rb +6 -2
  28. data/lib/grape/dsl/request_response.rb +23 -20
  29. data/lib/grape/dsl/routing.rb +52 -49
  30. data/lib/grape/dsl/settings.rb +110 -0
  31. data/lib/grape/dsl/validations.rb +14 -6
  32. data/lib/grape/endpoint.rb +104 -88
  33. data/lib/grape/exceptions/base.rb +2 -2
  34. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  35. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  36. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  37. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
  38. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  39. data/lib/grape/exceptions/missing_option.rb +1 -1
  40. data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
  41. data/lib/grape/exceptions/unknown_options.rb +1 -1
  42. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  43. data/lib/grape/exceptions/validation.rb +1 -1
  44. data/lib/grape/exceptions/validation_errors.rb +2 -2
  45. data/lib/grape/formatter/serializable_hash.rb +1 -1
  46. data/lib/grape/formatter/xml.rb +1 -1
  47. data/lib/grape/locale/en.yml +2 -0
  48. data/lib/grape/middleware/auth/dsl.rb +26 -21
  49. data/lib/grape/middleware/auth/strategies.rb +1 -1
  50. data/lib/grape/middleware/auth/strategy_info.rb +0 -2
  51. data/lib/grape/middleware/base.rb +2 -2
  52. data/lib/grape/middleware/error.rb +1 -1
  53. data/lib/grape/middleware/formatter.rb +5 -5
  54. data/lib/grape/middleware/versioner.rb +1 -1
  55. data/lib/grape/middleware/versioner/header.rb +3 -3
  56. data/lib/grape/middleware/versioner/param.rb +2 -2
  57. data/lib/grape/middleware/versioner/path.rb +1 -1
  58. data/lib/grape/namespace.rb +1 -1
  59. data/lib/grape/path.rb +9 -3
  60. data/lib/grape/util/content_types.rb +16 -8
  61. data/lib/grape/util/inheritable_setting.rb +74 -0
  62. data/lib/grape/util/inheritable_values.rb +51 -0
  63. data/lib/grape/util/stackable_values.rb +52 -0
  64. data/lib/grape/util/strict_hash_configuration.rb +106 -0
  65. data/lib/grape/validations.rb +0 -220
  66. data/lib/grape/validations/attributes_iterator.rb +21 -0
  67. data/lib/grape/validations/params_scope.rb +176 -0
  68. data/lib/grape/validations/validators/all_or_none.rb +20 -0
  69. data/lib/grape/validations/validators/allow_blank.rb +30 -0
  70. data/lib/grape/validations/validators/at_least_one_of.rb +20 -0
  71. data/lib/grape/validations/validators/base.rb +37 -0
  72. data/lib/grape/validations/{coerce.rb → validators/coerce.rb} +3 -3
  73. data/lib/grape/validations/{default.rb → validators/default.rb} +1 -1
  74. data/lib/grape/validations/validators/exactly_one_of.rb +20 -0
  75. data/lib/grape/validations/validators/multiple_params_base.rb +26 -0
  76. data/lib/grape/validations/validators/mutual_exclusion.rb +25 -0
  77. data/lib/grape/validations/{presence.rb → validators/presence.rb} +2 -2
  78. data/lib/grape/validations/validators/regexp.rb +12 -0
  79. data/lib/grape/validations/validators/values.rb +26 -0
  80. data/lib/grape/version.rb +1 -1
  81. data/spec/grape/api_spec.rb +522 -343
  82. data/spec/grape/dsl/callbacks_spec.rb +4 -4
  83. data/spec/grape/dsl/configuration_spec.rb +48 -9
  84. data/spec/grape/dsl/helpers_spec.rb +6 -13
  85. data/spec/grape/dsl/inside_route_spec.rb +43 -4
  86. data/spec/grape/dsl/middleware_spec.rb +1 -10
  87. data/spec/grape/dsl/parameters_spec.rb +8 -1
  88. data/spec/grape/dsl/request_response_spec.rb +16 -22
  89. data/spec/grape/dsl/routing_spec.rb +21 -5
  90. data/spec/grape/dsl/settings_spec.rb +219 -0
  91. data/spec/grape/dsl/validations_spec.rb +8 -11
  92. data/spec/grape/endpoint_spec.rb +115 -86
  93. data/spec/grape/entity_spec.rb +33 -33
  94. data/spec/grape/exceptions/invalid_formatter_spec.rb +3 -5
  95. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +4 -6
  96. data/spec/grape/exceptions/missing_mime_type_spec.rb +5 -6
  97. data/spec/grape/exceptions/missing_option_spec.rb +3 -5
  98. data/spec/grape/exceptions/unknown_options_spec.rb +3 -5
  99. data/spec/grape/exceptions/unknown_validator_spec.rb +3 -5
  100. data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
  101. data/spec/grape/loading_spec.rb +44 -0
  102. data/spec/grape/middleware/auth/base_spec.rb +0 -4
  103. data/spec/grape/middleware/auth/dsl_spec.rb +2 -4
  104. data/spec/grape/middleware/auth/strategies_spec.rb +5 -6
  105. data/spec/grape/middleware/exception_spec.rb +8 -10
  106. data/spec/grape/middleware/formatter_spec.rb +13 -15
  107. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +10 -10
  108. data/spec/grape/middleware/versioner/header_spec.rb +25 -25
  109. data/spec/grape/middleware/versioner/param_spec.rb +15 -17
  110. data/spec/grape/middleware/versioner/path_spec.rb +1 -2
  111. data/spec/grape/middleware/versioner_spec.rb +0 -1
  112. data/spec/grape/path_spec.rb +66 -45
  113. data/spec/grape/util/inheritable_setting_spec.rb +217 -0
  114. data/spec/grape/util/inheritable_values_spec.rb +63 -0
  115. data/spec/grape/util/stackable_values_spec.rb +115 -0
  116. data/spec/grape/util/strict_hash_configuration_spec.rb +38 -0
  117. data/spec/grape/validations/attributes_iterator_spec.rb +4 -0
  118. data/spec/grape/validations/params_scope_spec.rb +57 -0
  119. data/spec/grape/validations/validators/all_or_none_spec.rb +60 -0
  120. data/spec/grape/validations/validators/allow_blank_spec.rb +170 -0
  121. data/spec/grape/validations/{at_least_one_of_spec.rb → validators/at_least_one_of_spec.rb} +7 -3
  122. data/spec/grape/validations/{coerce_spec.rb → validators/coerce_spec.rb} +8 -11
  123. data/spec/grape/validations/{default_spec.rb → validators/default_spec.rb} +7 -9
  124. data/spec/grape/validations/{exactly_one_of_spec.rb → validators/exactly_one_of_spec.rb} +15 -11
  125. data/spec/grape/validations/{mutual_exclusion_spec.rb → validators/mutual_exclusion_spec.rb} +11 -9
  126. data/spec/grape/validations/{presence_spec.rb → validators/presence_spec.rb} +30 -30
  127. data/spec/grape/validations/{regexp_spec.rb → validators/regexp_spec.rb} +2 -4
  128. data/spec/grape/validations/{values_spec.rb → validators/values_spec.rb} +95 -23
  129. data/spec/grape/validations/{zh-CN.yml → validators/zh-CN.yml} +0 -0
  130. data/spec/grape/validations_spec.rb +335 -70
  131. data/spec/shared/versioning_examples.rb +7 -8
  132. data/spec/spec_helper.rb +2 -0
  133. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  134. data/spec/support/content_type_helpers.rb +1 -1
  135. data/spec/support/versioned_helpers.rb +3 -3
  136. metadata +80 -33
  137. data/lib/grape/util/deep_merge.rb +0 -23
  138. data/lib/grape/util/hash_stack.rb +0 -120
  139. data/lib/grape/validations/at_least_one_of.rb +0 -25
  140. data/lib/grape/validations/exactly_one_of.rb +0 -26
  141. data/lib/grape/validations/mutual_exclusion.rb +0 -25
  142. data/lib/grape/validations/regexp.rb +0 -12
  143. data/lib/grape/validations/values.rb +0 -23
  144. data/spec/grape/util/hash_stack_spec.rb +0 -132
@@ -0,0 +1,52 @@
1
+ module Grape
2
+ module Util
3
+ class StackableValues
4
+ attr_accessor :inherited_values
5
+ attr_reader :new_values
6
+ attr_reader :froozen_values
7
+
8
+ def initialize(inherited_values = {})
9
+ @inherited_values = inherited_values
10
+ @new_values = {}
11
+ @froozen_values = {}
12
+ end
13
+
14
+ def [](name)
15
+ return @froozen_values[name] if @froozen_values.key? name
16
+ [@inherited_values[name], @new_values[name]].compact.flatten(1)
17
+ end
18
+
19
+ def []=(name, value)
20
+ fail if @froozen_values.key? name
21
+ @new_values[name] ||= []
22
+ @new_values[name].push value
23
+ end
24
+
25
+ def delete(key)
26
+ new_values.delete key
27
+ end
28
+
29
+ attr_writer :new_values
30
+
31
+ def keys
32
+ (@new_values.keys + @inherited_values.keys).sort.uniq
33
+ end
34
+
35
+ def to_hash
36
+ keys.each_with_object({}) do |key, result|
37
+ result[key] = self[key]
38
+ end
39
+ end
40
+
41
+ def freeze_value(key)
42
+ @froozen_values[key] = self[key].freeze
43
+ end
44
+
45
+ def initialize_copy(other)
46
+ super
47
+ self.inherited_values = other.inherited_values
48
+ self.new_values = other.new_values.deep_dup
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,106 @@
1
+ module Grape
2
+ module Util
3
+ module StrictHashConfiguration
4
+ extend ActiveSupport::Concern
5
+
6
+ module DSL
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def settings
11
+ config_context.to_hash
12
+ end
13
+
14
+ def configure(&block)
15
+ config_context.instance_exec(&block)
16
+ end
17
+ end
18
+ end
19
+
20
+ class SettingsContainer
21
+ def initialize
22
+ @settings = {}
23
+ @contexts = {}
24
+ end
25
+
26
+ def to_hash
27
+ @settings.to_hash
28
+ end
29
+ end
30
+
31
+ def self.config_class(*args)
32
+ new_config_class = Class.new(SettingsContainer)
33
+
34
+ args.each do |setting_name|
35
+ if setting_name.respond_to? :values
36
+ nested_settings_methods(setting_name, new_config_class)
37
+ else
38
+ simple_settings_methods(setting_name, new_config_class)
39
+ end
40
+ end
41
+
42
+ new_config_class
43
+ end
44
+
45
+ def self.simple_settings_methods(setting_name, new_config_class)
46
+ setting_name_sym = setting_name.to_sym
47
+ new_config_class.class_eval do
48
+ define_method setting_name do |new_value|
49
+ @settings[setting_name_sym] = new_value
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.nested_settings_methods(setting_name, new_config_class)
55
+ new_config_class.class_eval do
56
+ setting_name.each_pair do |key, value|
57
+ define_method "#{key}_context" do
58
+ @contexts[key] ||= Grape::Util::StrictHashConfiguration.config_class(*value).new
59
+ end
60
+
61
+ define_method key do |&block|
62
+ send("#{key}_context").instance_exec(&block)
63
+ end
64
+ end
65
+
66
+ define_method 'to_hash' do
67
+ merge_hash = setting_name.keys.each_with_object({}) { |k, hash| hash[k] = send("#{k}_context").to_hash }
68
+
69
+ @settings.to_hash.merge(
70
+ merge_hash
71
+ )
72
+ end
73
+ end
74
+ end
75
+
76
+ def self.module(*args)
77
+ new_module = Module.new do
78
+ extend ActiveSupport::Concern
79
+ include DSL
80
+ end
81
+
82
+ new_module.tap do |mod|
83
+ class_mod = create_class_mod(args)
84
+
85
+ mod.const_set(:ClassMethods, class_mod)
86
+ end
87
+ end
88
+
89
+ def self.create_class_mod(args)
90
+ new_module = Module.new do
91
+ def config_context
92
+ @config_context ||= config_class.new
93
+ end
94
+ end
95
+
96
+ new_module.tap do |class_mod|
97
+ new_config_class = config_class(*args)
98
+
99
+ class_mod.send(:define_method, :config_class) do
100
+ @config_context ||= new_config_class
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -1,72 +1,5 @@
1
1
  module Grape
2
2
  module Validations
3
- ##
4
- # All validators must inherit from this class.
5
- #
6
- class Validator
7
- attr_reader :attrs
8
-
9
- def initialize(attrs, options, required, scope)
10
- @attrs = Array(attrs)
11
- @required = required
12
- @scope = scope
13
- end
14
-
15
- def validate!(params)
16
- attributes = AttributesIterator.new(self, @scope, params)
17
- attributes.each do |resource_params, attr_name|
18
- if @required || resource_params.key?(attr_name)
19
- validate_param!(attr_name, resource_params)
20
- end
21
- end
22
- end
23
-
24
- class AttributesIterator
25
- include Enumerable
26
-
27
- def initialize(validator, scope, params)
28
- @attrs = validator.attrs
29
- @params = scope.params(params)
30
- @params = (@params.is_a?(Array) ? @params : [@params])
31
- end
32
-
33
- def each
34
- @params.each do |resource_params|
35
- @attrs.each do |attr_name|
36
- yield resource_params, attr_name
37
- end
38
- end
39
- end
40
- end
41
-
42
- def self.convert_to_short_name(klass)
43
- ret = klass.name.gsub(/::/, '/')
44
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
45
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
46
- .tr("-", "_")
47
- .downcase
48
- File.basename(ret, '_validator')
49
- end
50
- end
51
-
52
- ##
53
- # Base class for all validators taking only one param.
54
- class SingleOptionValidator < Validator
55
- def initialize(attrs, options, required, scope)
56
- @option = options
57
- super
58
- end
59
- end
60
-
61
- # We define Validator::inherited here so SingleOptionValidator
62
- # will not be considered a validator.
63
- class Validator
64
- def self.inherited(klass)
65
- short_name = convert_to_short_name(klass)
66
- Validations.register_validator(short_name, klass)
67
- end
68
- end
69
-
70
3
  class << self
71
4
  attr_accessor :validators
72
5
  end
@@ -76,158 +9,5 @@ module Grape
76
9
  def self.register_validator(short_name, klass)
77
10
  validators[short_name] = klass
78
11
  end
79
-
80
- class ParamsScope
81
- attr_accessor :element, :parent
82
-
83
- include Grape::DSL::Parameters
84
-
85
- def initialize(opts, &block)
86
- @element = opts[:element]
87
- @parent = opts[:parent]
88
- @api = opts[:api]
89
- @optional = opts[:optional] || false
90
- @type = opts[:type]
91
- @declared_params = []
92
-
93
- instance_eval(&block) if block_given?
94
-
95
- configure_declared_params
96
- end
97
-
98
- def should_validate?(parameters)
99
- return false if @optional && params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
100
- return true if parent.nil?
101
- parent.should_validate?(parameters)
102
- end
103
-
104
- def full_name(name)
105
- return "#{@parent.full_name(@element)}[#{name}]" if @parent
106
- name.to_s
107
- end
108
-
109
- def root?
110
- !@parent
111
- end
112
-
113
- protected
114
-
115
- def push_declared_params(attrs)
116
- @declared_params.concat attrs
117
- end
118
-
119
- private
120
-
121
- def require_required_and_optional_fields(context, opts)
122
- if context == :all
123
- optional_fields = Array(opts[:except])
124
- required_fields = opts[:using].keys - optional_fields
125
- else # context == :none
126
- required_fields = Array(opts[:except])
127
- optional_fields = opts[:using].keys - required_fields
128
- end
129
- required_fields.each do |field|
130
- field_opts = opts[:using][field]
131
- raise ArgumentError, "required field not exist: #{field}" unless field_opts
132
- requires(field, field_opts)
133
- end
134
- optional_fields.each do |field|
135
- field_opts = opts[:using][field]
136
- optional(field, field_opts) if field_opts
137
- end
138
- end
139
-
140
- def validate_attributes(attrs, opts, &block)
141
- validations = { presence: true }
142
- validations.merge!(opts) if opts
143
- validations[:type] ||= Array if block
144
- validates(attrs, validations)
145
- end
146
-
147
- def new_scope(attrs, optional = false, &block)
148
- opts = attrs[1] || { type: Array }
149
- raise ArgumentError unless opts.keys.to_set.subset? [:type].to_set
150
- ParamsScope.new(api: @api, element: attrs.first, parent: self, optional: optional, type: opts[:type], &block)
151
- end
152
-
153
- # Pushes declared params to parent or settings
154
- def configure_declared_params
155
- if @parent
156
- @parent.push_declared_params [element => @declared_params]
157
- else
158
- @api.settings.peek[:declared_params] ||= []
159
- @api.settings[:declared_params].concat @declared_params
160
- end
161
- end
162
-
163
- def validates(attrs, validations)
164
- doc_attrs = { required: validations.keys.include?(:presence) }
165
-
166
- # special case (type = coerce)
167
- validations[:coerce] = validations.delete(:type) if validations.key?(:type)
168
-
169
- coerce_type = validations[:coerce]
170
- doc_attrs[:type] = coerce_type.to_s if coerce_type
171
-
172
- desc = validations.delete(:desc)
173
- doc_attrs[:desc] = desc if desc
174
-
175
- default = validations[:default]
176
- doc_attrs[:default] = default if default
177
-
178
- values = validations[:values]
179
- doc_attrs[:values] = values if values
180
-
181
- values = (values.is_a?(Proc) ? values.call : values)
182
-
183
- # default value should be present in values array, if both exist
184
- if default && values && !values.include?(default)
185
- raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
186
- end
187
-
188
- # type should be compatible with values array, if both exist
189
- if coerce_type && values && values.any? { |v| !v.kind_of?(coerce_type) }
190
- raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
191
- end
192
-
193
- doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
194
-
195
- full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
196
- @api.document_attribute(full_attrs, doc_attrs)
197
-
198
- # Validate for presence before any other validators
199
- if validations.key?(:presence) && validations[:presence]
200
- validate('presence', validations[:presence], attrs, doc_attrs)
201
- validations.delete(:presence)
202
- end
203
-
204
- # Before we run the rest of the validators, lets handle
205
- # whatever coercion so that we are working with correctly
206
- # type casted values
207
- if validations.key? :coerce
208
- validate('coerce', validations[:coerce], attrs, doc_attrs)
209
- validations.delete(:coerce)
210
- end
211
-
212
- validations.each do |type, options|
213
- validate(type, options, attrs, doc_attrs)
214
- end
215
- end
216
-
217
- def validate(type, options, attrs, doc_attrs)
218
- validator_class = Validations.validators[type.to_s]
219
-
220
- if validator_class
221
- (@api.settings.peek[:validations] ||= []) << validator_class.new(attrs, options, doc_attrs[:required], self)
222
- else
223
- raise Grape::Exceptions::UnknownValidator.new(type)
224
- end
225
- end
226
- end
227
12
  end
228
13
  end
229
-
230
- # Load all defined validations.
231
- Dir[File.expand_path('../validations/*.rb', __FILE__)].each do |path|
232
- require(path)
233
- end
@@ -0,0 +1,21 @@
1
+ module Grape
2
+ module Validations
3
+ class AttributesIterator
4
+ include Enumerable
5
+
6
+ def initialize(validator, scope, params)
7
+ @attrs = validator.attrs
8
+ @params = scope.params(params)
9
+ @params = (@params.is_a?(Array) ? @params : [@params])
10
+ end
11
+
12
+ def each
13
+ @params.each do |resource_params|
14
+ @attrs.each do |attr_name|
15
+ yield resource_params, attr_name
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,176 @@
1
+ module Grape
2
+ module Validations
3
+ class ParamsScope
4
+ attr_accessor :element, :parent
5
+
6
+ include Grape::DSL::Parameters
7
+
8
+ def initialize(opts, &block)
9
+ @element = opts[:element]
10
+ @parent = opts[:parent]
11
+ @api = opts[:api]
12
+ @optional = opts[:optional] || false
13
+ @type = opts[:type]
14
+ @declared_params = []
15
+
16
+ instance_eval(&block) if block_given?
17
+
18
+ configure_declared_params
19
+ end
20
+
21
+ def should_validate?(parameters)
22
+ return false if @optional && params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
23
+ return true if parent.nil?
24
+ parent.should_validate?(parameters)
25
+ end
26
+
27
+ def full_name(name)
28
+ return "#{@parent.full_name(@element)}[#{name}]" if @parent
29
+ name.to_s
30
+ end
31
+
32
+ def root?
33
+ !@parent
34
+ end
35
+
36
+ def required?
37
+ !@optional
38
+ end
39
+
40
+ protected
41
+
42
+ def push_declared_params(attrs)
43
+ @declared_params.concat attrs
44
+ end
45
+
46
+ private
47
+
48
+ def require_required_and_optional_fields(context, opts)
49
+ if context == :all
50
+ optional_fields = Array(opts[:except])
51
+ required_fields = opts[:using].keys - optional_fields
52
+ else # context == :none
53
+ required_fields = Array(opts[:except])
54
+ optional_fields = opts[:using].keys - required_fields
55
+ end
56
+ required_fields.each do |field|
57
+ field_opts = opts[:using][field]
58
+ fail ArgumentError, "required field not exist: #{field}" unless field_opts
59
+ requires(field, field_opts)
60
+ end
61
+ optional_fields.each do |field|
62
+ field_opts = opts[:using][field]
63
+ optional(field, field_opts) if field_opts
64
+ end
65
+ end
66
+
67
+ def validate_attributes(attrs, opts, &block)
68
+ validations = { presence: true }
69
+ validations.merge!(opts) if opts
70
+ validations[:type] ||= Array if block
71
+ validates(attrs, validations)
72
+ end
73
+
74
+ def new_scope(attrs, optional = false, &block)
75
+ opts = attrs[1] || { type: Array }
76
+ ParamsScope.new(api: @api, element: attrs.first, parent: self, optional: optional, type: opts[:type], &block)
77
+ end
78
+
79
+ # Pushes declared params to parent or settings
80
+ def configure_declared_params
81
+ if @parent
82
+ @parent.push_declared_params [element => @declared_params]
83
+ else
84
+ @api.namespace_stackable(:declared_params, @declared_params)
85
+
86
+ @api.route_setting(:declared_params, []) unless @api.route_setting(:declared_params)
87
+ @api.route_setting(:declared_params).concat @declared_params
88
+ end
89
+ end
90
+
91
+ def validates(attrs, validations)
92
+ doc_attrs = { required: validations.keys.include?(:presence) }
93
+
94
+ # special case (type = coerce)
95
+ validations[:coerce] = validations.delete(:type) if validations.key?(:type)
96
+
97
+ coerce_type = validations[:coerce]
98
+
99
+ doc_attrs[:type] = coerce_type.to_s if coerce_type
100
+
101
+ desc = validations.delete(:desc) || validations.delete(:description)
102
+ doc_attrs[:desc] = desc if desc
103
+
104
+ default = validations[:default]
105
+ doc_attrs[:default] = default if default
106
+
107
+ values = validations[:values]
108
+ doc_attrs[:values] = values if values
109
+
110
+ coerce_type = guess_coerce_type(coerce_type, values)
111
+
112
+ # default value should be present in values array, if both exist and are not procs
113
+ check_incompatible_option_values(values, default)
114
+
115
+ # type should be compatible with values array, if both exist
116
+ validate_value_coercion(coerce_type, values)
117
+
118
+ doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
119
+
120
+ full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
121
+ @api.document_attribute(full_attrs, doc_attrs)
122
+
123
+ # Validate for presence before any other validators
124
+ if validations.key?(:presence) && validations[:presence]
125
+ validate('presence', validations[:presence], attrs, doc_attrs)
126
+ validations.delete(:presence)
127
+ end
128
+
129
+ # Before we run the rest of the validators, lets handle
130
+ # whatever coercion so that we are working with correctly
131
+ # type casted values
132
+ if validations.key? :coerce
133
+ validate('coerce', validations[:coerce], attrs, doc_attrs)
134
+ validations.delete(:coerce)
135
+ end
136
+
137
+ validations.each do |type, options|
138
+ validate(type, options, attrs, doc_attrs)
139
+ end
140
+ end
141
+
142
+ def guess_coerce_type(coerce_type, values)
143
+ return coerce_type if !values || values.is_a?(Proc)
144
+ return values.first.class if coerce_type == Array && !values.empty?
145
+ coerce_type
146
+ end
147
+
148
+ def check_incompatible_option_values(values, default)
149
+ return unless values && default
150
+ return if values.is_a?(Proc) || default.is_a?(Proc)
151
+ return if values.include?(default)
152
+ fail Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
153
+ end
154
+
155
+ def validate(type, options, attrs, doc_attrs)
156
+ validator_class = Validations.validators[type.to_s]
157
+
158
+ if validator_class
159
+ value = validator_class.new(attrs, options, doc_attrs[:required], self)
160
+ @api.namespace_stackable(:validations, value)
161
+ else
162
+ fail Grape::Exceptions::UnknownValidator.new(type)
163
+ end
164
+ end
165
+
166
+ def validate_value_coercion(coerce_type, values)
167
+ return unless coerce_type && values
168
+ return if values.is_a?(Proc)
169
+ coerce_type = coerce_type.first if coerce_type.kind_of?(Array)
170
+ if values.any? { |v| !v.kind_of?(coerce_type) }
171
+ fail Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end