grape 0.3.0 → 0.7.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 +15 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +70 -0
- data/.travis.yml +7 -6
- data/CHANGELOG.md +134 -4
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +5 -2
- data/README.md +551 -116
- data/RELEASING.md +105 -0
- data/Rakefile +29 -8
- data/UPGRADING.md +124 -0
- data/grape.gemspec +3 -3
- data/lib/grape/api.rb +207 -88
- data/lib/grape/cookies.rb +4 -8
- data/lib/grape/endpoint.rb +198 -144
- data/lib/grape/error_formatter/base.rb +5 -7
- data/lib/grape/error_formatter/json.rb +3 -5
- data/lib/grape/error_formatter/txt.rb +1 -3
- data/lib/grape/error_formatter/xml.rb +4 -6
- data/lib/grape/exceptions/base.rb +9 -9
- data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
- data/lib/grape/exceptions/invalid_formatter.rb +1 -4
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -5
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -6
- data/lib/grape/exceptions/missing_mime_type.rb +1 -5
- data/lib/grape/exceptions/missing_option.rb +1 -4
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -4
- data/lib/grape/exceptions/unknown_options.rb +1 -5
- data/lib/grape/exceptions/unknown_validator.rb +1 -3
- data/lib/grape/exceptions/validation.rb +13 -3
- data/lib/grape/exceptions/validation_errors.rb +43 -0
- data/lib/grape/formatter/base.rb +5 -7
- data/lib/grape/formatter/json.rb +0 -3
- data/lib/grape/formatter/serializable_hash.rb +15 -15
- data/lib/grape/formatter/txt.rb +0 -2
- data/lib/grape/formatter/xml.rb +0 -2
- data/lib/grape/http/request.rb +26 -0
- data/lib/grape/locale/en.yml +8 -5
- data/lib/grape/middleware/auth/base.rb +30 -0
- data/lib/grape/middleware/auth/basic.rb +3 -20
- data/lib/grape/middleware/auth/digest.rb +2 -19
- data/lib/grape/middleware/auth/oauth2.rb +31 -24
- data/lib/grape/middleware/base.rb +7 -7
- data/lib/grape/middleware/error.rb +36 -22
- data/lib/grape/middleware/filter.rb +3 -3
- data/lib/grape/middleware/formatter.rb +99 -61
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
- data/lib/grape/middleware/versioner/header.rb +22 -16
- data/lib/grape/middleware/versioner/param.rb +9 -11
- data/lib/grape/middleware/versioner/path.rb +10 -13
- data/lib/grape/middleware/versioner.rb +3 -1
- data/lib/grape/namespace.rb +23 -0
- data/lib/grape/parser/base.rb +3 -5
- data/lib/grape/parser/json.rb +0 -2
- data/lib/grape/parser/xml.rb +0 -2
- data/lib/grape/path.rb +70 -0
- data/lib/grape/route.rb +10 -6
- data/lib/grape/util/content_types.rb +2 -1
- data/lib/grape/util/deep_merge.rb +5 -5
- data/lib/grape/util/hash_stack.rb +13 -2
- data/lib/grape/validations/coerce.rb +11 -10
- data/lib/grape/validations/default.rb +25 -0
- data/lib/grape/validations/presence.rb +7 -3
- data/lib/grape/validations/regexp.rb +2 -5
- data/lib/grape/validations/values.rb +17 -0
- data/lib/grape/validations.rb +161 -54
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +19 -4
- data/spec/grape/api_spec.rb +897 -268
- data/spec/grape/endpoint_spec.rb +283 -66
- data/spec/grape/entity_spec.rb +132 -29
- data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/basic_spec.rb +8 -8
- data/spec/grape/middleware/auth/digest_spec.rb +5 -5
- data/spec/grape/middleware/auth/oauth2_spec.rb +81 -36
- data/spec/grape/middleware/base_spec.rb +8 -13
- data/spec/grape/middleware/error_spec.rb +13 -17
- data/spec/grape/middleware/exception_spec.rb +47 -27
- data/spec/grape/middleware/formatter_spec.rb +103 -41
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
- data/spec/grape/middleware/versioner/header_spec.rb +76 -51
- data/spec/grape/middleware/versioner/param_spec.rb +18 -18
- data/spec/grape/middleware/versioner/path_spec.rb +6 -6
- data/spec/grape/middleware/versioner_spec.rb +5 -2
- data/spec/grape/path_spec.rb +229 -0
- data/spec/grape/util/hash_stack_spec.rb +31 -32
- data/spec/grape/validations/coerce_spec.rb +116 -51
- data/spec/grape/validations/default_spec.rb +123 -0
- data/spec/grape/validations/presence_spec.rb +42 -44
- data/spec/grape/validations/regexp_spec.rb +9 -9
- data/spec/grape/validations/values_spec.rb +138 -0
- data/spec/grape/validations/zh-CN.yml +4 -3
- data/spec/grape/validations_spec.rb +681 -48
- data/spec/shared/versioning_examples.rb +22 -6
- data/spec/spec_helper.rb +3 -2
- data/spec/support/basic_auth_encode_helpers.rb +0 -1
- data/spec/support/content_type_helpers.rb +11 -0
- data/spec/support/versioned_helpers.rb +13 -5
- metadata +34 -84
data/lib/grape/parser/base.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
module Grape
|
2
2
|
module Parser
|
3
3
|
module Base
|
4
|
-
|
5
4
|
class << self
|
6
|
-
|
7
5
|
PARSERS = {
|
8
|
-
:
|
9
|
-
:
|
6
|
+
json: Grape::Parser::Json,
|
7
|
+
jsonapi: Grape::Parser::Json,
|
8
|
+
xml: Grape::Parser::Xml
|
10
9
|
}
|
11
10
|
|
12
11
|
def parsers(options)
|
@@ -24,7 +23,6 @@ module Grape
|
|
24
23
|
spec
|
25
24
|
end
|
26
25
|
end
|
27
|
-
|
28
26
|
end
|
29
27
|
end
|
30
28
|
end
|
data/lib/grape/parser/json.rb
CHANGED
data/lib/grape/parser/xml.rb
CHANGED
data/lib/grape/path.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Grape
|
2
|
+
class Path
|
3
|
+
def self.prepare(raw_path, namespace, settings)
|
4
|
+
Path.new(raw_path, namespace, settings).path_with_suffix
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :raw_path, :namespace, :settings
|
8
|
+
|
9
|
+
def initialize(raw_path, namespace, settings)
|
10
|
+
@raw_path = raw_path
|
11
|
+
@namespace = namespace
|
12
|
+
@settings = settings
|
13
|
+
end
|
14
|
+
|
15
|
+
def mount_path
|
16
|
+
split_setting(:mount_path, '/')
|
17
|
+
end
|
18
|
+
|
19
|
+
def root_prefix
|
20
|
+
split_setting(:root_prefix, '/')
|
21
|
+
end
|
22
|
+
|
23
|
+
def uses_path_versioning?
|
24
|
+
!!(settings[:version] && settings[:version_options][:using] == :path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def has_namespace?
|
28
|
+
namespace && namespace.to_s =~ /^\S/ && namespace != '/'
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_path?
|
32
|
+
raw_path && raw_path.to_s =~ /^\S/ && raw_path != '/'
|
33
|
+
end
|
34
|
+
|
35
|
+
def suffix
|
36
|
+
if !uses_path_versioning? || (has_namespace? || has_path?)
|
37
|
+
'(.:format)'
|
38
|
+
else
|
39
|
+
'(/.:format)'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def path
|
44
|
+
Rack::Mount::Utils.normalize_path(parts.join('/'))
|
45
|
+
end
|
46
|
+
|
47
|
+
def path_with_suffix
|
48
|
+
"#{path}#{suffix}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
path_with_suffix
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def parts
|
58
|
+
parts = [mount_path, root_prefix].compact
|
59
|
+
parts << ':version' if uses_path_versioning?
|
60
|
+
parts << namespace.to_s
|
61
|
+
parts << raw_path.to_s
|
62
|
+
parts.flatten.reject { |part| part == '/' }
|
63
|
+
end
|
64
|
+
|
65
|
+
def split_setting(key, delimiter)
|
66
|
+
return if settings[key].nil?
|
67
|
+
settings[key].to_s.split("/")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/grape/route.rb
CHANGED
@@ -1,23 +1,27 @@
|
|
1
1
|
module Grape
|
2
|
-
|
3
2
|
# A compiled route for inspection.
|
4
3
|
class Route
|
5
|
-
|
6
4
|
def initialize(options = {})
|
7
5
|
@options = options || {}
|
8
6
|
end
|
9
|
-
|
7
|
+
|
10
8
|
def method_missing(method_id, *arguments)
|
11
|
-
|
9
|
+
match = /route_([_a-zA-Z]\w*)/.match(method_id.to_s)
|
10
|
+
if match
|
12
11
|
@options[match.captures.last.to_sym]
|
13
12
|
else
|
14
13
|
super
|
15
14
|
end
|
16
15
|
end
|
17
|
-
|
16
|
+
|
18
17
|
def to_s
|
19
18
|
"version=#{route_version}, method=#{route_method}, path=#{route_path}"
|
20
19
|
end
|
21
|
-
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def to_ary
|
24
|
+
nil
|
25
|
+
end
|
22
26
|
end
|
23
27
|
end
|
@@ -5,10 +5,11 @@ module Grape
|
|
5
5
|
:xml, 'application/xml',
|
6
6
|
:serializable_hash, 'application/json',
|
7
7
|
:json, 'application/json',
|
8
|
+
:jsonapi, 'application/vnd.api+json',
|
8
9
|
:atom, 'application/atom+xml',
|
9
10
|
:rss, 'application/rss+xml',
|
10
11
|
:txt, 'text/plain',
|
11
|
-
|
12
|
+
]
|
12
13
|
|
13
14
|
def self.content_types_for(from_settings)
|
14
15
|
from_settings || Grape::ContentTypes::CONTENT_TYPES
|
@@ -3,18 +3,18 @@ class Hash
|
|
3
3
|
# activesupport/lib/active_support/core_ext/hash/deep_merge.rb
|
4
4
|
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
5
5
|
#
|
6
|
-
# h1 = {:
|
7
|
-
# h2 = {:
|
6
|
+
# h1 = {x: {y: [4,5,6]}, z: [7,8,9]}
|
7
|
+
# h2 = {x: {y: [7,8,9]}, z: "xyz"}
|
8
8
|
#
|
9
|
-
# h1.deep_merge(h2) #=> { :
|
10
|
-
# h2.deep_merge(h1) #=> { :
|
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
11
|
def deep_merge(other_hash)
|
12
12
|
dup.deep_merge!(other_hash)
|
13
13
|
end
|
14
14
|
|
15
15
|
# Same as +deep_merge+, but modifies +self+.
|
16
16
|
def deep_merge!(other_hash)
|
17
|
-
other_hash.each_pair do |k,v|
|
17
|
+
other_hash.each_pair do |k, v|
|
18
18
|
tv = self[k]
|
19
19
|
self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
|
20
20
|
end
|
@@ -42,6 +42,17 @@ module Grape
|
|
42
42
|
end
|
43
43
|
alias_method :[], :get
|
44
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 has_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
|
+
|
45
56
|
# Replace a value on the top hash of the stack.
|
46
57
|
#
|
47
58
|
# @param key [Symbol] The key to set.
|
@@ -73,7 +84,7 @@ module Grape
|
|
73
84
|
|
74
85
|
# Prepend another HashStack's to self
|
75
86
|
def prepend(hash_stack)
|
76
|
-
@stack.unshift
|
87
|
+
@stack.unshift(*hash_stack.stack)
|
77
88
|
self
|
78
89
|
end
|
79
90
|
|
@@ -89,7 +100,7 @@ module Grape
|
|
89
100
|
# @param key [Symbol] The key to gather
|
90
101
|
# @return [Array]
|
91
102
|
def gather(key)
|
92
|
-
stack.
|
103
|
+
stack.flat_map { |s| s[key] }.compact.uniq
|
93
104
|
end
|
94
105
|
|
95
106
|
def to_s
|
@@ -1,24 +1,23 @@
|
|
1
1
|
module Grape
|
2
|
-
|
3
2
|
class API
|
4
|
-
Boolean = Virtus::Attribute::Boolean
|
3
|
+
Boolean = Virtus::Attribute::Boolean # rubocop:disable ConstantName
|
5
4
|
end
|
6
5
|
|
7
6
|
module Validations
|
8
|
-
|
9
7
|
class CoerceValidator < SingleOptionValidator
|
10
8
|
def validate_param!(attr_name, params)
|
9
|
+
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message_key: :coerce unless params.is_a? Hash
|
11
10
|
new_value = coerce_value(@option, params[attr_name])
|
12
11
|
if valid_type?(new_value)
|
13
12
|
params[attr_name] = new_value
|
14
13
|
else
|
15
|
-
raise Grape::Exceptions::Validation, :
|
16
|
-
:param => @scope.full_name(attr_name), :message_key => :coerce
|
14
|
+
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message_key: :coerce
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
20
18
|
class InvalidValue; end
|
21
|
-
|
19
|
+
|
20
|
+
private
|
22
21
|
|
23
22
|
def _valid_array_type?(type, values)
|
24
23
|
values.all? do |val|
|
@@ -47,16 +46,18 @@ module Grape
|
|
47
46
|
end
|
48
47
|
|
49
48
|
def coerce_value(type, val)
|
50
|
-
|
49
|
+
# Don't coerce things other than nil to Arrays or Hashes
|
50
|
+
return val || [] if type == Array
|
51
|
+
return val || {} if type == Hash
|
52
|
+
|
53
|
+
converter = Virtus::Attribute.build(type)
|
51
54
|
converter.coerce(val)
|
52
55
|
|
53
56
|
# not the prettiest but some invalid coercion can currently trigger
|
54
|
-
# errors in Virtus (see coerce_spec.rb)
|
57
|
+
# errors in Virtus (see coerce_spec.rb:75)
|
55
58
|
rescue
|
56
59
|
InvalidValue.new
|
57
60
|
end
|
58
|
-
|
59
61
|
end
|
60
|
-
|
61
62
|
end
|
62
63
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
class DefaultValidator < Validator
|
4
|
+
def initialize(attrs, options, required, scope)
|
5
|
+
@default = options
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate_param!(attr_name, params)
|
10
|
+
params[attr_name] = @default.is_a?(Proc) ? @default.call : @default unless params.has_key?(attr_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate!(params)
|
14
|
+
attrs = AttributesIterator.new(self, @scope, params)
|
15
|
+
parent_element = @scope.element
|
16
|
+
attrs.each do |resource_params, attr_name|
|
17
|
+
if resource_params[attr_name].nil?
|
18
|
+
validate_param!(attr_name, resource_params)
|
19
|
+
params[parent_element] = resource_params if parent_element && params[parent_element].nil?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,10 +1,14 @@
|
|
1
1
|
module Grape
|
2
2
|
module Validations
|
3
3
|
class PresenceValidator < Validator
|
4
|
+
def validate!(params)
|
5
|
+
return unless @scope.should_validate?(params)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
4
9
|
def validate_param!(attr_name, params)
|
5
|
-
unless params.has_key?(attr_name)
|
6
|
-
raise Grape::Exceptions::Validation, :
|
7
|
-
:param => @scope.full_name(attr_name), :message_key => :presence
|
10
|
+
unless params.respond_to?(:has_key?) && params.has_key?(attr_name)
|
11
|
+
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message_key: :presence
|
8
12
|
end
|
9
13
|
end
|
10
14
|
end
|
@@ -1,14 +1,11 @@
|
|
1
1
|
module Grape
|
2
2
|
module Validations
|
3
|
-
|
4
3
|
class RegexpValidator < SingleOptionValidator
|
5
4
|
def validate_param!(attr_name, params)
|
6
|
-
if params[attr_name] && !(
|
7
|
-
raise Grape::Exceptions::Validation, :
|
8
|
-
:param => @scope.full_name(attr_name), :message_key => :regexp
|
5
|
+
if params[attr_name] && !(params[attr_name].to_s =~ @option)
|
6
|
+
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message_key: :regexp
|
9
7
|
end
|
10
8
|
end
|
11
9
|
end
|
12
|
-
|
13
10
|
end
|
14
11
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
class ValuesValidator < Validator
|
4
|
+
def initialize(attrs, options, required, scope)
|
5
|
+
@values = options
|
6
|
+
@required = required
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate_param!(attr_name, params)
|
11
|
+
if (params[attr_name] || @required) && !(@values.is_a?(Proc) ? @values.call : @values).include?(params[attr_name])
|
12
|
+
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message_key: :values
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/grape/validations.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module Grape
|
2
|
-
|
3
2
|
module Validations
|
4
|
-
|
5
3
|
##
|
6
4
|
# All validators must inherit from this class.
|
7
5
|
#
|
@@ -19,25 +17,40 @@ module Grape
|
|
19
17
|
end
|
20
18
|
|
21
19
|
def validate!(params)
|
22
|
-
|
20
|
+
attributes = AttributesIterator.new(self, @scope, params)
|
21
|
+
attributes.each do |resource_params, attr_name|
|
22
|
+
if @required || resource_params.has_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
|
23
36
|
|
24
|
-
|
25
|
-
@
|
26
|
-
|
27
|
-
|
37
|
+
def each
|
38
|
+
@params.each do |resource_params|
|
39
|
+
@attrs.each do |attr_name|
|
40
|
+
yield resource_params, attr_name
|
28
41
|
end
|
29
42
|
end
|
30
43
|
end
|
31
44
|
end
|
32
45
|
|
33
|
-
|
46
|
+
private
|
34
47
|
|
35
48
|
def self.convert_to_short_name(klass)
|
36
|
-
ret = klass.name.gsub(/::/, '/')
|
37
|
-
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
38
|
-
gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
39
|
-
tr("-", "_")
|
40
|
-
downcase
|
49
|
+
ret = klass.name.gsub(/::/, '/')
|
50
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
51
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
52
|
+
.tr("-", "_")
|
53
|
+
.downcase
|
41
54
|
File.basename(ret, '_validator')
|
42
55
|
end
|
43
56
|
end
|
@@ -49,7 +62,6 @@ module Grape
|
|
49
62
|
@option = options
|
50
63
|
super
|
51
64
|
end
|
52
|
-
|
53
65
|
end
|
54
66
|
|
55
67
|
# We define Validator::inherited here so SingleOptionValidator
|
@@ -57,7 +69,7 @@ module Grape
|
|
57
69
|
class Validator
|
58
70
|
def self.inherited(klass)
|
59
71
|
short_name = convert_to_short_name(klass)
|
60
|
-
Validations
|
72
|
+
Validations.register_validator(short_name, klass)
|
61
73
|
end
|
62
74
|
end
|
63
75
|
|
@@ -74,71 +86,173 @@ module Grape
|
|
74
86
|
class ParamsScope
|
75
87
|
attr_accessor :element, :parent
|
76
88
|
|
77
|
-
def initialize(
|
78
|
-
@element
|
79
|
-
@parent
|
80
|
-
@api
|
89
|
+
def initialize(opts, &block)
|
90
|
+
@element = opts[:element]
|
91
|
+
@parent = opts[:parent]
|
92
|
+
@api = opts[:api]
|
93
|
+
@optional = opts[:optional] || false
|
94
|
+
@type = opts[:type]
|
95
|
+
@declared_params = []
|
96
|
+
|
81
97
|
instance_eval(&block)
|
82
|
-
end
|
83
98
|
|
84
|
-
|
85
|
-
|
86
|
-
if attrs.last.is_a?(Hash)
|
87
|
-
validations.merge!(attrs.pop)
|
88
|
-
end
|
99
|
+
configure_declared_params
|
100
|
+
end
|
89
101
|
|
90
|
-
|
91
|
-
|
102
|
+
def should_validate?(parameters)
|
103
|
+
return false if @optional && params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
|
104
|
+
return true if parent.nil?
|
105
|
+
parent.should_validate?(parameters)
|
92
106
|
end
|
93
107
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
|
108
|
+
def requires(*attrs, &block)
|
109
|
+
orig_attrs = attrs.clone
|
110
|
+
|
111
|
+
opts = attrs.last.is_a?(Hash) ? attrs.pop : nil
|
112
|
+
|
113
|
+
if opts && opts[:using]
|
114
|
+
require_required_and_optional_fields(attrs.first, opts)
|
115
|
+
else
|
116
|
+
validate_attributes(attrs, opts, &block)
|
117
|
+
|
118
|
+
block_given? ? new_scope(orig_attrs, &block) :
|
119
|
+
push_declared_params(attrs)
|
98
120
|
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def optional(*attrs, &block)
|
124
|
+
orig_attrs = attrs
|
99
125
|
|
100
|
-
|
126
|
+
validations = {}
|
127
|
+
validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
|
128
|
+
validations[:type] ||= Array if block_given?
|
101
129
|
validates(attrs, validations)
|
130
|
+
|
131
|
+
block_given? ? new_scope(orig_attrs, true, &block) :
|
132
|
+
push_declared_params(attrs)
|
102
133
|
end
|
103
134
|
|
104
|
-
def group(
|
105
|
-
|
135
|
+
def group(*attrs, &block)
|
136
|
+
requires(*attrs, &block)
|
106
137
|
end
|
107
138
|
|
108
139
|
def params(params)
|
109
140
|
params = @parent.params(params) if @parent
|
110
|
-
|
141
|
+
if @element
|
142
|
+
if params.is_a?(Array)
|
143
|
+
params = params.flat_map { |el| el[@element] || {} }
|
144
|
+
elsif params.is_a?(Hash)
|
145
|
+
params = params[@element] || {}
|
146
|
+
else
|
147
|
+
params = {}
|
148
|
+
end
|
149
|
+
end
|
111
150
|
params
|
112
151
|
end
|
113
152
|
|
153
|
+
def use(*names)
|
154
|
+
named_params = @api.settings[:named_params] || {}
|
155
|
+
names.each do |name|
|
156
|
+
params_block = named_params.fetch(name) do
|
157
|
+
raise "Params :#{name} not found!"
|
158
|
+
end
|
159
|
+
instance_eval(¶ms_block)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
alias_method :use_scope, :use
|
163
|
+
alias_method :includes, :use
|
164
|
+
|
114
165
|
def full_name(name)
|
115
166
|
return "#{@parent.full_name(@element)}[#{name}]" if @parent
|
116
167
|
name.to_s
|
117
168
|
end
|
118
169
|
|
119
|
-
|
170
|
+
protected
|
171
|
+
|
172
|
+
def push_declared_params(attrs)
|
173
|
+
@declared_params.concat attrs
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def require_required_and_optional_fields(context, opts)
|
179
|
+
if context == :all
|
180
|
+
optional_fields = Array(opts[:except])
|
181
|
+
required_fields = opts[:using].keys - optional_fields
|
182
|
+
else # context == :none
|
183
|
+
required_fields = Array(opts[:except])
|
184
|
+
optional_fields = opts[:using].keys - required_fields
|
185
|
+
end
|
186
|
+
required_fields.each do |field|
|
187
|
+
requires(field, opts[:using][field])
|
188
|
+
end
|
189
|
+
optional_fields.each do |field|
|
190
|
+
optional(field, opts[:using][field])
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def validate_attributes(attrs, opts, &block)
|
195
|
+
validations = { presence: true }
|
196
|
+
validations.merge!(opts) if opts
|
197
|
+
validations[:type] ||= Array if block
|
198
|
+
validates(attrs, validations)
|
199
|
+
end
|
200
|
+
|
201
|
+
def new_scope(attrs, optional = false, &block)
|
202
|
+
opts = attrs[1] || { type: Array }
|
203
|
+
raise ArgumentError unless opts.keys.to_set.subset? [:type].to_set
|
204
|
+
ParamsScope.new(api: @api, element: attrs.first, parent: self, optional: optional, type: opts[:type], &block)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Pushes declared params to parent or settings
|
208
|
+
def configure_declared_params
|
209
|
+
if @parent
|
210
|
+
@parent.push_declared_params [element => @declared_params]
|
211
|
+
else
|
212
|
+
@api.settings.peek[:declared_params] ||= []
|
213
|
+
@api.settings[:declared_params].concat @declared_params
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
120
217
|
def validates(attrs, validations)
|
121
|
-
doc_attrs = { :
|
218
|
+
doc_attrs = { required: validations.keys.include?(:presence) }
|
122
219
|
|
123
220
|
# special case (type = coerce)
|
124
|
-
if validations
|
125
|
-
|
126
|
-
|
221
|
+
validations[:coerce] = validations.delete(:type) if validations.key?(:type)
|
222
|
+
|
223
|
+
coerce_type = validations[:coerce]
|
224
|
+
doc_attrs[:type] = coerce_type.to_s if coerce_type
|
225
|
+
|
226
|
+
desc = validations.delete(:desc)
|
227
|
+
doc_attrs[:desc] = desc if desc
|
228
|
+
|
229
|
+
default = validations[:default]
|
230
|
+
doc_attrs[:default] = default if default
|
231
|
+
|
232
|
+
values = validations[:values]
|
233
|
+
doc_attrs[:values] = values if values
|
127
234
|
|
128
|
-
|
129
|
-
|
235
|
+
values = (values.is_a?(Proc) ? values.call : values)
|
236
|
+
|
237
|
+
# default value should be present in values array, if both exist
|
238
|
+
if default && values && !values.include?(default)
|
239
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
|
130
240
|
end
|
131
241
|
|
132
|
-
if
|
133
|
-
|
242
|
+
# type should be compatible with values array, if both exist
|
243
|
+
if coerce_type && values && values.any? { |v| !v.kind_of?(coerce_type) }
|
244
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
|
134
245
|
end
|
135
246
|
|
136
|
-
|
247
|
+
doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
|
248
|
+
|
249
|
+
full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
|
137
250
|
@api.document_attribute(full_attrs, doc_attrs)
|
138
251
|
|
139
252
|
# Validate for presence before any other validators
|
140
253
|
if validations.has_key?(:presence) && validations[:presence]
|
141
254
|
validate('presence', validations[:presence], attrs, doc_attrs)
|
255
|
+
validations.delete(:presence)
|
142
256
|
end
|
143
257
|
|
144
258
|
# Before we run the rest of the validators, lets handle
|
@@ -155,7 +269,7 @@ module Grape
|
|
155
269
|
end
|
156
270
|
|
157
271
|
def validate(type, options, attrs, doc_attrs)
|
158
|
-
validator_class = Validations
|
272
|
+
validator_class = Validations.validators[type.to_s]
|
159
273
|
|
160
274
|
if validator_class
|
161
275
|
(@api.settings.peek[:validations] ||= []) << validator_class.new(attrs, options, doc_attrs[:required], self)
|
@@ -163,11 +277,6 @@ module Grape
|
|
163
277
|
raise Grape::Exceptions::UnknownValidator.new(type)
|
164
278
|
end
|
165
279
|
end
|
166
|
-
|
167
|
-
def push_declared_params(attrs)
|
168
|
-
@api.settings.peek[:declared_params] ||= []
|
169
|
-
@api.settings[:declared_params] += attrs
|
170
|
-
end
|
171
280
|
end
|
172
281
|
|
173
282
|
# This module is mixed into the API Class.
|
@@ -178,7 +287,7 @@ module Grape
|
|
178
287
|
end
|
179
288
|
|
180
289
|
def params(&block)
|
181
|
-
ParamsScope.new(self,
|
290
|
+
ParamsScope.new(api: self, type: Hash, &block)
|
182
291
|
end
|
183
292
|
|
184
293
|
def document_attribute(names, opts)
|
@@ -189,9 +298,7 @@ module Grape
|
|
189
298
|
@last_description[:params][name[:full_name].to_s].merge!(opts)
|
190
299
|
end
|
191
300
|
end
|
192
|
-
|
193
301
|
end
|
194
|
-
|
195
302
|
end
|
196
303
|
end
|
197
304
|
|
data/lib/grape/version.rb
CHANGED