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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -66
- data/.rubocop_todo.yml +78 -17
- data/.travis.yml +7 -3
- data/Appraisals +7 -0
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +7 -0
- data/Gemfile +1 -7
- data/Guardfile +1 -1
- data/README.md +560 -94
- data/RELEASING.md +1 -1
- data/Rakefile +10 -11
- data/UPGRADING.md +211 -3
- data/gemfiles/rails_3.gemfile +14 -0
- data/gemfiles/rails_4.gemfile +14 -0
- data/grape.gemspec +10 -9
- data/lib/backports/active_support/deep_dup.rb +49 -0
- data/lib/backports/active_support/duplicable.rb +88 -0
- data/lib/grape.rb +29 -2
- data/lib/grape/api.rb +59 -65
- data/lib/grape/dsl/api.rb +19 -0
- data/lib/grape/dsl/callbacks.rb +6 -4
- data/lib/grape/dsl/configuration.rb +49 -5
- data/lib/grape/dsl/helpers.rb +7 -8
- data/lib/grape/dsl/inside_route.rb +22 -10
- data/lib/grape/dsl/middleware.rb +5 -5
- data/lib/grape/dsl/parameters.rb +6 -2
- data/lib/grape/dsl/request_response.rb +23 -20
- data/lib/grape/dsl/routing.rb +52 -49
- data/lib/grape/dsl/settings.rb +110 -0
- data/lib/grape/dsl/validations.rb +14 -6
- data/lib/grape/endpoint.rb +104 -88
- data/lib/grape/exceptions/base.rb +2 -2
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/missing_option.rb +1 -1
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
- data/lib/grape/exceptions/unknown_options.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +2 -2
- data/lib/grape/formatter/serializable_hash.rb +1 -1
- data/lib/grape/formatter/xml.rb +1 -1
- data/lib/grape/locale/en.yml +2 -0
- data/lib/grape/middleware/auth/dsl.rb +26 -21
- data/lib/grape/middleware/auth/strategies.rb +1 -1
- data/lib/grape/middleware/auth/strategy_info.rb +0 -2
- data/lib/grape/middleware/base.rb +2 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +5 -5
- data/lib/grape/middleware/versioner.rb +1 -1
- data/lib/grape/middleware/versioner/header.rb +3 -3
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +1 -1
- data/lib/grape/path.rb +9 -3
- data/lib/grape/util/content_types.rb +16 -8
- data/lib/grape/util/inheritable_setting.rb +74 -0
- data/lib/grape/util/inheritable_values.rb +51 -0
- data/lib/grape/util/stackable_values.rb +52 -0
- data/lib/grape/util/strict_hash_configuration.rb +106 -0
- data/lib/grape/validations.rb +0 -220
- data/lib/grape/validations/attributes_iterator.rb +21 -0
- data/lib/grape/validations/params_scope.rb +176 -0
- data/lib/grape/validations/validators/all_or_none.rb +20 -0
- data/lib/grape/validations/validators/allow_blank.rb +30 -0
- data/lib/grape/validations/validators/at_least_one_of.rb +20 -0
- data/lib/grape/validations/validators/base.rb +37 -0
- data/lib/grape/validations/{coerce.rb → validators/coerce.rb} +3 -3
- data/lib/grape/validations/{default.rb → validators/default.rb} +1 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +20 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +26 -0
- data/lib/grape/validations/validators/mutual_exclusion.rb +25 -0
- data/lib/grape/validations/{presence.rb → validators/presence.rb} +2 -2
- data/lib/grape/validations/validators/regexp.rb +12 -0
- data/lib/grape/validations/validators/values.rb +26 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +522 -343
- data/spec/grape/dsl/callbacks_spec.rb +4 -4
- data/spec/grape/dsl/configuration_spec.rb +48 -9
- data/spec/grape/dsl/helpers_spec.rb +6 -13
- data/spec/grape/dsl/inside_route_spec.rb +43 -4
- data/spec/grape/dsl/middleware_spec.rb +1 -10
- data/spec/grape/dsl/parameters_spec.rb +8 -1
- data/spec/grape/dsl/request_response_spec.rb +16 -22
- data/spec/grape/dsl/routing_spec.rb +21 -5
- data/spec/grape/dsl/settings_spec.rb +219 -0
- data/spec/grape/dsl/validations_spec.rb +8 -11
- data/spec/grape/endpoint_spec.rb +115 -86
- data/spec/grape/entity_spec.rb +33 -33
- data/spec/grape/exceptions/invalid_formatter_spec.rb +3 -5
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +4 -6
- data/spec/grape/exceptions/missing_mime_type_spec.rb +5 -6
- data/spec/grape/exceptions/missing_option_spec.rb +3 -5
- data/spec/grape/exceptions/unknown_options_spec.rb +3 -5
- data/spec/grape/exceptions/unknown_validator_spec.rb +3 -5
- data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
- data/spec/grape/loading_spec.rb +44 -0
- data/spec/grape/middleware/auth/base_spec.rb +0 -4
- data/spec/grape/middleware/auth/dsl_spec.rb +2 -4
- data/spec/grape/middleware/auth/strategies_spec.rb +5 -6
- data/spec/grape/middleware/exception_spec.rb +8 -10
- data/spec/grape/middleware/formatter_spec.rb +13 -15
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +10 -10
- data/spec/grape/middleware/versioner/header_spec.rb +25 -25
- data/spec/grape/middleware/versioner/param_spec.rb +15 -17
- data/spec/grape/middleware/versioner/path_spec.rb +1 -2
- data/spec/grape/middleware/versioner_spec.rb +0 -1
- data/spec/grape/path_spec.rb +66 -45
- data/spec/grape/util/inheritable_setting_spec.rb +217 -0
- data/spec/grape/util/inheritable_values_spec.rb +63 -0
- data/spec/grape/util/stackable_values_spec.rb +115 -0
- data/spec/grape/util/strict_hash_configuration_spec.rb +38 -0
- data/spec/grape/validations/attributes_iterator_spec.rb +4 -0
- data/spec/grape/validations/params_scope_spec.rb +57 -0
- data/spec/grape/validations/validators/all_or_none_spec.rb +60 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +170 -0
- data/spec/grape/validations/{at_least_one_of_spec.rb → validators/at_least_one_of_spec.rb} +7 -3
- data/spec/grape/validations/{coerce_spec.rb → validators/coerce_spec.rb} +8 -11
- data/spec/grape/validations/{default_spec.rb → validators/default_spec.rb} +7 -9
- data/spec/grape/validations/{exactly_one_of_spec.rb → validators/exactly_one_of_spec.rb} +15 -11
- data/spec/grape/validations/{mutual_exclusion_spec.rb → validators/mutual_exclusion_spec.rb} +11 -9
- data/spec/grape/validations/{presence_spec.rb → validators/presence_spec.rb} +30 -30
- data/spec/grape/validations/{regexp_spec.rb → validators/regexp_spec.rb} +2 -4
- data/spec/grape/validations/{values_spec.rb → validators/values_spec.rb} +95 -23
- data/spec/grape/validations/{zh-CN.yml → validators/zh-CN.yml} +0 -0
- data/spec/grape/validations_spec.rb +335 -70
- data/spec/shared/versioning_examples.rb +7 -8
- data/spec/spec_helper.rb +2 -0
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/content_type_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +3 -3
- metadata +80 -33
- data/lib/grape/util/deep_merge.rb +0 -23
- data/lib/grape/util/hash_stack.rb +0 -120
- data/lib/grape/validations/at_least_one_of.rb +0 -25
- data/lib/grape/validations/exactly_one_of.rb +0 -26
- data/lib/grape/validations/mutual_exclusion.rb +0 -25
- data/lib/grape/validations/regexp.rb +0 -12
- data/lib/grape/validations/values.rb +0 -23
- 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
|
data/lib/grape/validations.rb
CHANGED
@@ -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
|