grape-security 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/.rspec +2 -0
- data/.rubocop.yml +70 -0
- data/.travis.yml +18 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +314 -0
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +21 -0
- data/Guardfile +14 -0
- data/LICENSE +20 -0
- data/README.md +1777 -0
- data/RELEASING.md +105 -0
- data/Rakefile +69 -0
- data/UPGRADING.md +124 -0
- data/grape-security.gemspec +39 -0
- data/grape.png +0 -0
- data/lib/grape.rb +99 -0
- data/lib/grape/api.rb +646 -0
- data/lib/grape/cookies.rb +39 -0
- data/lib/grape/endpoint.rb +533 -0
- data/lib/grape/error_formatter/base.rb +31 -0
- data/lib/grape/error_formatter/json.rb +15 -0
- data/lib/grape/error_formatter/txt.rb +16 -0
- data/lib/grape/error_formatter/xml.rb +15 -0
- data/lib/grape/exceptions/base.rb +66 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
- data/lib/grape/exceptions/invalid_formatter.rb +10 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +10 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +10 -0
- data/lib/grape/exceptions/missing_mime_type.rb +10 -0
- data/lib/grape/exceptions/missing_option.rb +10 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +10 -0
- data/lib/grape/exceptions/unknown_options.rb +10 -0
- data/lib/grape/exceptions/unknown_validator.rb +10 -0
- data/lib/grape/exceptions/validation.rb +26 -0
- data/lib/grape/exceptions/validation_errors.rb +43 -0
- data/lib/grape/formatter/base.rb +31 -0
- data/lib/grape/formatter/json.rb +12 -0
- data/lib/grape/formatter/serializable_hash.rb +35 -0
- data/lib/grape/formatter/txt.rb +11 -0
- data/lib/grape/formatter/xml.rb +12 -0
- data/lib/grape/http/request.rb +26 -0
- data/lib/grape/locale/en.yml +32 -0
- data/lib/grape/middleware/auth/base.rb +30 -0
- data/lib/grape/middleware/auth/basic.rb +13 -0
- data/lib/grape/middleware/auth/digest.rb +13 -0
- data/lib/grape/middleware/auth/oauth2.rb +83 -0
- data/lib/grape/middleware/base.rb +62 -0
- data/lib/grape/middleware/error.rb +89 -0
- data/lib/grape/middleware/filter.rb +17 -0
- data/lib/grape/middleware/formatter.rb +150 -0
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner.rb +32 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
- data/lib/grape/middleware/versioner/header.rb +132 -0
- data/lib/grape/middleware/versioner/param.rb +42 -0
- data/lib/grape/middleware/versioner/path.rb +52 -0
- data/lib/grape/namespace.rb +23 -0
- data/lib/grape/parser/base.rb +29 -0
- data/lib/grape/parser/json.rb +11 -0
- data/lib/grape/parser/xml.rb +11 -0
- data/lib/grape/path.rb +70 -0
- data/lib/grape/route.rb +27 -0
- data/lib/grape/util/content_types.rb +18 -0
- data/lib/grape/util/deep_merge.rb +23 -0
- data/lib/grape/util/hash_stack.rb +120 -0
- data/lib/grape/validations.rb +322 -0
- data/lib/grape/validations/coerce.rb +63 -0
- data/lib/grape/validations/default.rb +25 -0
- data/lib/grape/validations/exactly_one_of.rb +26 -0
- data/lib/grape/validations/mutual_exclusion.rb +25 -0
- data/lib/grape/validations/presence.rb +16 -0
- data/lib/grape/validations/regexp.rb +12 -0
- data/lib/grape/validations/values.rb +23 -0
- data/lib/grape/version.rb +3 -0
- data/spec/grape/api_spec.rb +2571 -0
- data/spec/grape/endpoint_spec.rb +784 -0
- data/spec/grape/entity_spec.rb +324 -0
- data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +18 -0
- data/spec/grape/exceptions/missing_option_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/basic_spec.rb +31 -0
- data/spec/grape/middleware/auth/digest_spec.rb +47 -0
- data/spec/grape/middleware/auth/oauth2_spec.rb +135 -0
- data/spec/grape/middleware/base_spec.rb +58 -0
- data/spec/grape/middleware/error_spec.rb +45 -0
- data/spec/grape/middleware/exception_spec.rb +184 -0
- data/spec/grape/middleware/formatter_spec.rb +258 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
- data/spec/grape/middleware/versioner/header_spec.rb +302 -0
- data/spec/grape/middleware/versioner/param_spec.rb +58 -0
- data/spec/grape/middleware/versioner/path_spec.rb +44 -0
- data/spec/grape/middleware/versioner_spec.rb +22 -0
- data/spec/grape/path_spec.rb +229 -0
- data/spec/grape/util/hash_stack_spec.rb +132 -0
- data/spec/grape/validations/coerce_spec.rb +208 -0
- data/spec/grape/validations/default_spec.rb +123 -0
- data/spec/grape/validations/exactly_one_of_spec.rb +71 -0
- data/spec/grape/validations/mutual_exclusion_spec.rb +61 -0
- data/spec/grape/validations/presence_spec.rb +142 -0
- data/spec/grape/validations/regexp_spec.rb +40 -0
- data/spec/grape/validations/values_spec.rb +152 -0
- data/spec/grape/validations/zh-CN.yml +10 -0
- data/spec/grape/validations_spec.rb +994 -0
- data/spec/shared/versioning_examples.rb +121 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/basic_auth_encode_helpers.rb +3 -0
- data/spec/support/content_type_helpers.rb +11 -0
- data/spec/support/versioned_helpers.rb +50 -0
- metadata +421 -0
data/lib/grape/route.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Grape
|
2
|
+
# A compiled route for inspection.
|
3
|
+
class Route
|
4
|
+
def initialize(options = {})
|
5
|
+
@options = options || {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(method_id, *arguments)
|
9
|
+
match = /route_([_a-zA-Z]\w*)/.match(method_id.to_s)
|
10
|
+
if match
|
11
|
+
@options[match.captures.last.to_sym]
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"version=#{route_version}, method=#{route_method}, path=#{route_path}"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def to_ary
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Grape
|
2
|
+
module ContentTypes
|
3
|
+
# Content types are listed in order of preference.
|
4
|
+
CONTENT_TYPES = ActiveSupport::OrderedHash[
|
5
|
+
:xml, 'application/xml',
|
6
|
+
:serializable_hash, 'application/json',
|
7
|
+
:json, 'application/json',
|
8
|
+
:jsonapi, 'application/vnd.api+json',
|
9
|
+
:atom, 'application/atom+xml',
|
10
|
+
:rss, 'application/rss+xml',
|
11
|
+
:txt, 'text/plain',
|
12
|
+
]
|
13
|
+
|
14
|
+
def self.content_types_for(from_settings)
|
15
|
+
from_settings || Grape::ContentTypes::CONTENT_TYPES
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Hash
|
2
|
+
# deep_merge from rails
|
3
|
+
# activesupport/lib/active_support/core_ext/hash/deep_merge.rb
|
4
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
5
|
+
#
|
6
|
+
# h1 = {x: {y: [4,5,6]}, z: [7,8,9]}
|
7
|
+
# h2 = {x: {y: [7,8,9]}, z: "xyz"}
|
8
|
+
#
|
9
|
+
# h1.deep_merge(h2) #=> { x: {y: [7, 8, 9]}, z: "xyz" }
|
10
|
+
# h2.deep_merge(h1) #=> { x: {y: [4, 5, 6]}, z: [7, 8, 9] }
|
11
|
+
def deep_merge(other_hash)
|
12
|
+
dup.deep_merge!(other_hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Same as +deep_merge+, but modifies +self+.
|
16
|
+
def deep_merge!(other_hash)
|
17
|
+
other_hash.each_pair do |k, v|
|
18
|
+
tv = self[k]
|
19
|
+
self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Grape
|
2
|
+
module Util
|
3
|
+
# HashStack is a stack of hashes. When retrieving a value, keys of the top
|
4
|
+
# hash on the stack take precendent over the lower keys.
|
5
|
+
class HashStack
|
6
|
+
# Unmerged array of hashes to represent the stack.
|
7
|
+
# The top of the stack is the last element.
|
8
|
+
attr_reader :stack
|
9
|
+
|
10
|
+
# TODO: handle aggregates
|
11
|
+
def initialize
|
12
|
+
@stack = [{}]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the top hash on the stack
|
16
|
+
def peek
|
17
|
+
@stack.last
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add a new hash to the top of the stack.
|
21
|
+
#
|
22
|
+
# @param hash [Hash] optional hash to be pushed. Defaults to empty hash
|
23
|
+
# @return [HashStack]
|
24
|
+
def push(hash = {})
|
25
|
+
@stack.push(hash)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def pop
|
30
|
+
@stack.pop
|
31
|
+
end
|
32
|
+
|
33
|
+
# Looks through the stack for the first frame that matches :key
|
34
|
+
#
|
35
|
+
# @param key [Symbol] key to look for in hash frames
|
36
|
+
# @return value of given key after merging the stack
|
37
|
+
def get(key)
|
38
|
+
(@stack.length - 1).downto(0).each do |i|
|
39
|
+
return @stack[i][key] if @stack[i].key? key
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
alias_method :[], :get
|
44
|
+
|
45
|
+
# Looks through the stack for the first frame that matches :key
|
46
|
+
#
|
47
|
+
# @param key [Symbol] key to look for in hash frames
|
48
|
+
# @return true if key exists, false otherwise
|
49
|
+
def key?(key)
|
50
|
+
(@stack.length - 1).downto(0).each do |i|
|
51
|
+
return true if @stack[i].key? key
|
52
|
+
end
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
# Replace a value on the top hash of the stack.
|
57
|
+
#
|
58
|
+
# @param key [Symbol] The key to set.
|
59
|
+
# @param value [Object] The value to set.
|
60
|
+
def set(key, value)
|
61
|
+
peek[key.to_sym] = value
|
62
|
+
end
|
63
|
+
alias_method :[]=, :set
|
64
|
+
|
65
|
+
# Replace multiple values on the top hash of the stack.
|
66
|
+
#
|
67
|
+
# @param hash [Hash] Hash of values to be merged in.
|
68
|
+
def update(hash)
|
69
|
+
peek.merge!(hash)
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
# Adds addition value into the top hash of the stack
|
74
|
+
def imbue(key, value)
|
75
|
+
current = peek[key.to_sym]
|
76
|
+
if current.is_a?(Array)
|
77
|
+
current.concat(value)
|
78
|
+
elsif current.is_a?(Hash)
|
79
|
+
current.merge!(value)
|
80
|
+
else
|
81
|
+
set(key, value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Prepend another HashStack's to self
|
86
|
+
def prepend(hash_stack)
|
87
|
+
@stack.unshift(*hash_stack.stack)
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# Concatenate another HashStack's to self
|
92
|
+
def concat(hash_stack)
|
93
|
+
@stack.concat hash_stack.stack
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
# Looks through the stack for all instances of a given key and returns
|
98
|
+
# them as a flat Array.
|
99
|
+
#
|
100
|
+
# @param key [Symbol] The key to gather
|
101
|
+
# @return [Array]
|
102
|
+
def gather(key)
|
103
|
+
stack.flat_map { |s| s[key] }.compact.uniq
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_s
|
107
|
+
@stack.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
def clone
|
111
|
+
new_stack = HashStack.new
|
112
|
+
stack.each do |frame|
|
113
|
+
new_stack.push frame.clone
|
114
|
+
end
|
115
|
+
new_stack.stack.shift
|
116
|
+
new_stack
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,322 @@
|
|
1
|
+
module Grape
|
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
|
+
|
14
|
+
if options.is_a?(Hash) && !options.empty?
|
15
|
+
raise Grape::Exceptions.UnknownOptions.new(options.keys)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate!(params)
|
20
|
+
attributes = AttributesIterator.new(self, @scope, params)
|
21
|
+
attributes.each do |resource_params, attr_name|
|
22
|
+
if @required || resource_params.key?(attr_name)
|
23
|
+
validate_param!(attr_name, resource_params)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class AttributesIterator
|
29
|
+
include Enumerable
|
30
|
+
|
31
|
+
def initialize(validator, scope, params)
|
32
|
+
@attrs = validator.attrs
|
33
|
+
@params = scope.params(params)
|
34
|
+
@params = (@params.is_a?(Array) ? @params : [@params])
|
35
|
+
end
|
36
|
+
|
37
|
+
def each
|
38
|
+
@params.each do |resource_params|
|
39
|
+
@attrs.each do |attr_name|
|
40
|
+
yield resource_params, attr_name
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.convert_to_short_name(klass)
|
47
|
+
ret = klass.name.gsub(/::/, '/')
|
48
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
49
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
50
|
+
.tr("-", "_")
|
51
|
+
.downcase
|
52
|
+
File.basename(ret, '_validator')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Base class for all validators taking only one param.
|
58
|
+
class SingleOptionValidator < Validator
|
59
|
+
def initialize(attrs, options, required, scope)
|
60
|
+
@option = options
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# We define Validator::inherited here so SingleOptionValidator
|
66
|
+
# will not be considered a validator.
|
67
|
+
class Validator
|
68
|
+
def self.inherited(klass)
|
69
|
+
short_name = convert_to_short_name(klass)
|
70
|
+
Validations.register_validator(short_name, klass)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class << self
|
75
|
+
attr_accessor :validators
|
76
|
+
end
|
77
|
+
|
78
|
+
self.validators = {}
|
79
|
+
|
80
|
+
def self.register_validator(short_name, klass)
|
81
|
+
validators[short_name] = klass
|
82
|
+
end
|
83
|
+
|
84
|
+
class ParamsScope
|
85
|
+
attr_accessor :element, :parent
|
86
|
+
|
87
|
+
def initialize(opts, &block)
|
88
|
+
@element = opts[:element]
|
89
|
+
@parent = opts[:parent]
|
90
|
+
@api = opts[:api]
|
91
|
+
@optional = opts[:optional] || false
|
92
|
+
@type = opts[:type]
|
93
|
+
@declared_params = []
|
94
|
+
|
95
|
+
instance_eval(&block)
|
96
|
+
|
97
|
+
configure_declared_params
|
98
|
+
end
|
99
|
+
|
100
|
+
def should_validate?(parameters)
|
101
|
+
return false if @optional && params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
|
102
|
+
return true if parent.nil?
|
103
|
+
parent.should_validate?(parameters)
|
104
|
+
end
|
105
|
+
|
106
|
+
def requires(*attrs, &block)
|
107
|
+
orig_attrs = attrs.clone
|
108
|
+
|
109
|
+
opts = attrs.last.is_a?(Hash) ? attrs.pop : nil
|
110
|
+
|
111
|
+
if opts && opts[:using]
|
112
|
+
require_required_and_optional_fields(attrs.first, opts)
|
113
|
+
else
|
114
|
+
validate_attributes(attrs, opts, &block)
|
115
|
+
|
116
|
+
block_given? ? new_scope(orig_attrs, &block) :
|
117
|
+
push_declared_params(attrs)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def optional(*attrs, &block)
|
122
|
+
orig_attrs = attrs
|
123
|
+
|
124
|
+
validations = {}
|
125
|
+
validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
|
126
|
+
validations[:type] ||= Array if block_given?
|
127
|
+
validates(attrs, validations)
|
128
|
+
|
129
|
+
block_given? ? new_scope(orig_attrs, true, &block) :
|
130
|
+
push_declared_params(attrs)
|
131
|
+
end
|
132
|
+
|
133
|
+
def mutually_exclusive(*attrs)
|
134
|
+
validates(attrs, mutual_exclusion: true)
|
135
|
+
end
|
136
|
+
|
137
|
+
def exactly_one_of(*attrs)
|
138
|
+
validates(attrs, exactly_one_of: true)
|
139
|
+
end
|
140
|
+
|
141
|
+
def group(*attrs, &block)
|
142
|
+
requires(*attrs, &block)
|
143
|
+
end
|
144
|
+
|
145
|
+
def params(params)
|
146
|
+
params = @parent.params(params) if @parent
|
147
|
+
if @element
|
148
|
+
if params.is_a?(Array)
|
149
|
+
params = params.flat_map { |el| el[@element] || {} }
|
150
|
+
elsif params.is_a?(Hash)
|
151
|
+
params = params[@element] || {}
|
152
|
+
else
|
153
|
+
params = {}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
params
|
157
|
+
end
|
158
|
+
|
159
|
+
def use(*names)
|
160
|
+
named_params = @api.settings[:named_params] || {}
|
161
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
162
|
+
names.each do |name|
|
163
|
+
params_block = named_params.fetch(name) do
|
164
|
+
raise "Params :#{name} not found!"
|
165
|
+
end
|
166
|
+
instance_exec(options, ¶ms_block)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
alias_method :use_scope, :use
|
170
|
+
alias_method :includes, :use
|
171
|
+
|
172
|
+
def full_name(name)
|
173
|
+
return "#{@parent.full_name(@element)}[#{name}]" if @parent
|
174
|
+
name.to_s
|
175
|
+
end
|
176
|
+
|
177
|
+
def root?
|
178
|
+
!@parent
|
179
|
+
end
|
180
|
+
|
181
|
+
protected
|
182
|
+
|
183
|
+
def push_declared_params(attrs)
|
184
|
+
@declared_params.concat attrs
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def require_required_and_optional_fields(context, opts)
|
190
|
+
if context == :all
|
191
|
+
optional_fields = Array(opts[:except])
|
192
|
+
required_fields = opts[:using].keys - optional_fields
|
193
|
+
else # context == :none
|
194
|
+
required_fields = Array(opts[:except])
|
195
|
+
optional_fields = opts[:using].keys - required_fields
|
196
|
+
end
|
197
|
+
required_fields.each do |field|
|
198
|
+
field_opts = opts[:using][field]
|
199
|
+
raise ArgumentError, "required field not exist: #{field}" unless field_opts
|
200
|
+
requires(field, field_opts)
|
201
|
+
end
|
202
|
+
optional_fields.each do |field|
|
203
|
+
field_opts = opts[:using][field]
|
204
|
+
optional(field, field_opts) if field_opts
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def validate_attributes(attrs, opts, &block)
|
209
|
+
validations = { presence: true }
|
210
|
+
validations.merge!(opts) if opts
|
211
|
+
validations[:type] ||= Array if block
|
212
|
+
validates(attrs, validations)
|
213
|
+
end
|
214
|
+
|
215
|
+
def new_scope(attrs, optional = false, &block)
|
216
|
+
opts = attrs[1] || { type: Array }
|
217
|
+
raise ArgumentError unless opts.keys.to_set.subset? [:type].to_set
|
218
|
+
ParamsScope.new(api: @api, element: attrs.first, parent: self, optional: optional, type: opts[:type], &block)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Pushes declared params to parent or settings
|
222
|
+
def configure_declared_params
|
223
|
+
if @parent
|
224
|
+
@parent.push_declared_params [element => @declared_params]
|
225
|
+
else
|
226
|
+
@api.settings.peek[:declared_params] ||= []
|
227
|
+
@api.settings[:declared_params].concat @declared_params
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def validates(attrs, validations)
|
232
|
+
doc_attrs = { required: validations.keys.include?(:presence) }
|
233
|
+
|
234
|
+
# special case (type = coerce)
|
235
|
+
validations[:coerce] = validations.delete(:type) if validations.key?(:type)
|
236
|
+
|
237
|
+
coerce_type = validations[:coerce]
|
238
|
+
doc_attrs[:type] = coerce_type.to_s if coerce_type
|
239
|
+
|
240
|
+
desc = validations.delete(:desc)
|
241
|
+
doc_attrs[:desc] = desc if desc
|
242
|
+
|
243
|
+
default = validations[:default]
|
244
|
+
doc_attrs[:default] = default if default
|
245
|
+
|
246
|
+
values = validations[:values]
|
247
|
+
doc_attrs[:values] = values if values
|
248
|
+
|
249
|
+
values = (values.is_a?(Proc) ? values.call : values)
|
250
|
+
|
251
|
+
# default value should be present in values array, if both exist
|
252
|
+
if default && values && !values.include?(default)
|
253
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
|
254
|
+
end
|
255
|
+
|
256
|
+
# type should be compatible with values array, if both exist
|
257
|
+
if coerce_type && values && values.any? { |v| !v.kind_of?(coerce_type) }
|
258
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
|
259
|
+
end
|
260
|
+
|
261
|
+
doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
|
262
|
+
|
263
|
+
full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
|
264
|
+
@api.document_attribute(full_attrs, doc_attrs)
|
265
|
+
|
266
|
+
# Validate for presence before any other validators
|
267
|
+
if validations.key?(:presence) && validations[:presence]
|
268
|
+
validate('presence', validations[:presence], attrs, doc_attrs)
|
269
|
+
validations.delete(:presence)
|
270
|
+
end
|
271
|
+
|
272
|
+
# Before we run the rest of the validators, lets handle
|
273
|
+
# whatever coercion so that we are working with correctly
|
274
|
+
# type casted values
|
275
|
+
if validations.key? :coerce
|
276
|
+
validate('coerce', validations[:coerce], attrs, doc_attrs)
|
277
|
+
validations.delete(:coerce)
|
278
|
+
end
|
279
|
+
|
280
|
+
validations.each do |type, options|
|
281
|
+
validate(type, options, attrs, doc_attrs)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def validate(type, options, attrs, doc_attrs)
|
286
|
+
validator_class = Validations.validators[type.to_s]
|
287
|
+
|
288
|
+
if validator_class
|
289
|
+
(@api.settings.peek[:validations] ||= []) << validator_class.new(attrs, options, doc_attrs[:required], self)
|
290
|
+
else
|
291
|
+
raise Grape::Exceptions::UnknownValidator.new(type)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# This module is mixed into the API Class.
|
297
|
+
module ClassMethods
|
298
|
+
def reset_validations!
|
299
|
+
settings.peek[:declared_params] = []
|
300
|
+
settings.peek[:validations] = []
|
301
|
+
end
|
302
|
+
|
303
|
+
def params(&block)
|
304
|
+
ParamsScope.new(api: self, type: Hash, &block)
|
305
|
+
end
|
306
|
+
|
307
|
+
def document_attribute(names, opts)
|
308
|
+
@last_description ||= {}
|
309
|
+
@last_description[:params] ||= {}
|
310
|
+
Array(names).each do |name|
|
311
|
+
@last_description[:params][name[:full_name].to_s] ||= {}
|
312
|
+
@last_description[:params][name[:full_name].to_s].merge!(opts)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# Load all defined validations.
|
320
|
+
Dir[File.expand_path('../validations/*.rb', __FILE__)].each do |path|
|
321
|
+
require(path)
|
322
|
+
end
|