grape 0.6.1 → 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.

Files changed (79) hide show
  1. checksums.yaml +9 -9
  2. data/.gitignore +7 -0
  3. data/.rubocop.yml +5 -0
  4. data/.travis.yml +3 -3
  5. data/CHANGELOG.md +42 -3
  6. data/CONTRIBUTING.md +118 -0
  7. data/Gemfile +4 -4
  8. data/README.md +312 -52
  9. data/Rakefile +6 -1
  10. data/UPGRADING.md +124 -0
  11. data/lib/grape.rb +2 -0
  12. data/lib/grape/api.rb +95 -44
  13. data/lib/grape/cookies.rb +0 -2
  14. data/lib/grape/endpoint.rb +63 -39
  15. data/lib/grape/error_formatter/base.rb +0 -3
  16. data/lib/grape/error_formatter/json.rb +0 -2
  17. data/lib/grape/error_formatter/txt.rb +0 -2
  18. data/lib/grape/error_formatter/xml.rb +0 -2
  19. data/lib/grape/exceptions/base.rb +0 -2
  20. data/lib/grape/exceptions/incompatible_option_values.rb +0 -3
  21. data/lib/grape/exceptions/invalid_formatter.rb +0 -3
  22. data/lib/grape/exceptions/invalid_versioner_option.rb +0 -4
  23. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +0 -2
  24. data/lib/grape/exceptions/missing_mime_type.rb +0 -4
  25. data/lib/grape/exceptions/missing_option.rb +0 -3
  26. data/lib/grape/exceptions/missing_vendor_option.rb +0 -3
  27. data/lib/grape/exceptions/unknown_options.rb +0 -4
  28. data/lib/grape/exceptions/unknown_validator.rb +0 -2
  29. data/lib/grape/exceptions/validation_errors.rb +6 -5
  30. data/lib/grape/formatter/base.rb +0 -3
  31. data/lib/grape/formatter/json.rb +0 -2
  32. data/lib/grape/formatter/serializable_hash.rb +15 -16
  33. data/lib/grape/formatter/txt.rb +0 -2
  34. data/lib/grape/formatter/xml.rb +0 -2
  35. data/lib/grape/http/request.rb +2 -4
  36. data/lib/grape/locale/en.yml +1 -1
  37. data/lib/grape/middleware/auth/oauth2.rb +15 -6
  38. data/lib/grape/middleware/base.rb +7 -7
  39. data/lib/grape/middleware/error.rb +11 -6
  40. data/lib/grape/middleware/formatter.rb +80 -78
  41. data/lib/grape/middleware/globals.rb +13 -0
  42. data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
  43. data/lib/grape/middleware/versioner/header.rb +5 -3
  44. data/lib/grape/middleware/versioner/param.rb +2 -4
  45. data/lib/grape/middleware/versioner/path.rb +3 -4
  46. data/lib/grape/namespace.rb +0 -1
  47. data/lib/grape/parser/base.rb +0 -3
  48. data/lib/grape/parser/json.rb +0 -2
  49. data/lib/grape/parser/xml.rb +0 -2
  50. data/lib/grape/path.rb +1 -3
  51. data/lib/grape/route.rb +0 -3
  52. data/lib/grape/util/hash_stack.rb +1 -1
  53. data/lib/grape/validations.rb +72 -22
  54. data/lib/grape/validations/coerce.rb +5 -4
  55. data/lib/grape/validations/default.rb +5 -3
  56. data/lib/grape/validations/presence.rb +1 -1
  57. data/lib/grape/validations/regexp.rb +0 -2
  58. data/lib/grape/validations/values.rb +2 -1
  59. data/lib/grape/version.rb +1 -1
  60. data/spec/grape/api_spec.rb +385 -96
  61. data/spec/grape/endpoint_spec.rb +162 -15
  62. data/spec/grape/entity_spec.rb +25 -0
  63. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  64. data/spec/grape/middleware/auth/oauth2_spec.rb +60 -15
  65. data/spec/grape/middleware/base_spec.rb +3 -8
  66. data/spec/grape/middleware/error_spec.rb +2 -2
  67. data/spec/grape/middleware/exception_spec.rb +4 -4
  68. data/spec/grape/middleware/formatter_spec.rb +7 -4
  69. data/spec/grape/middleware/versioner/param_spec.rb +8 -7
  70. data/spec/grape/path_spec.rb +24 -14
  71. data/spec/grape/util/hash_stack_spec.rb +8 -8
  72. data/spec/grape/validations/coerce_spec.rb +75 -33
  73. data/spec/grape/validations/default_spec.rb +57 -0
  74. data/spec/grape/validations/presence_spec.rb +13 -11
  75. data/spec/grape/validations/values_spec.rb +76 -2
  76. data/spec/grape/validations_spec.rb +443 -20
  77. data/spec/spec_helper.rb +2 -2
  78. data/spec/support/content_type_helpers.rb +11 -0
  79. metadata +9 -38
@@ -3,18 +3,18 @@ require 'grape/middleware/base'
3
3
  module Grape
4
4
  module Middleware
5
5
  class Error < Base
6
-
7
6
  def default_options
8
7
  {
9
- default_status: 403, # default status returned on error
8
+ default_status: 500, # default status returned on error
10
9
  default_message: "",
11
10
  format: :txt,
12
11
  formatters: {},
13
12
  error_formatters: {},
14
13
  rescue_all: false, # true to rescue all exceptions
14
+ rescue_subclasses: true, # rescue subclasses of exceptions listed
15
15
  rescue_options: { backtrace: false }, # true to display backtrace
16
16
  rescue_handlers: {}, # rescue handler blocks
17
- rescued_errors: []
17
+ base_only_rescue_handlers: {} # rescue handler blocks rescuing only the base class
18
18
  }
19
19
  end
20
20
 
@@ -31,15 +31,21 @@ module Grape
31
31
  handler = lambda { |arg| error_response(arg) }
32
32
  else
33
33
  raise unless is_rescuable
34
- handler = options[:rescue_handlers][e.class] || options[:rescue_handlers][:all]
34
+ handler = find_handler(e.class)
35
35
  end
36
36
 
37
37
  handler.nil? ? handle_error(e) : exec_handler(e, &handler)
38
38
  end
39
39
  end
40
40
 
41
+ def find_handler(klass)
42
+ handler = options[:rescue_handlers].find(-> { [] }) { |error, _| klass <= error }[1]
43
+ handler = options[:base_only_rescue_handlers][klass] || options[:base_only_rescue_handlers][:all] unless handler
44
+ handler
45
+ end
46
+
41
47
  def rescuable?(klass)
42
- options[:rescue_all] || (options[:rescued_errors] || []).include?(klass)
48
+ options[:rescue_all] || (options[:rescue_handlers] || []).any? { |error, handler| klass <= error } || (options[:base_only_rescue_handlers] || []).include?(klass)
43
49
  end
44
50
 
45
51
  def exec_handler(e, &handler)
@@ -73,7 +79,6 @@ module Grape
73
79
  throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
74
80
  formatter.call(message, backtrace, options, env)
75
81
  end
76
-
77
82
  end
78
83
  end
79
84
  end
@@ -3,7 +3,6 @@ require 'grape/middleware/base'
3
3
  module Grape
4
4
  module Middleware
5
5
  class Formatter < Base
6
-
7
6
  def default_options
8
7
  {
9
8
  default_format: :txt,
@@ -42,107 +41,110 @@ module Grape
42
41
 
43
42
  private
44
43
 
45
- # store read input in env['api.request.input']
46
- def read_body_input
47
- if (request.post? || request.put? || request.patch? || request.delete?) &&
48
- (!request.form_data? || !request.media_type) &&
49
- (!request.parseable_data?) &&
50
- (request.content_length.to_i > 0 || request.env['HTTP_TRANSFER_ENCODING'] == 'chunked')
44
+ def request
45
+ @request ||= Rack::Request.new(env)
46
+ end
51
47
 
52
- if (input = env['rack.input'])
48
+ # store read input in env['api.request.input']
49
+ def read_body_input
50
+ if (request.post? || request.put? || request.patch? || request.delete?) &&
51
+ (!request.form_data? || !request.media_type) &&
52
+ (!request.parseable_data?) &&
53
+ (request.content_length.to_i > 0 || request.env['HTTP_TRANSFER_ENCODING'] == 'chunked')
54
+
55
+ if (input = env['rack.input'])
56
+ input.rewind
57
+ body = env['api.request.input'] = input.read
58
+ begin
59
+ read_rack_input(body) if body && body.length > 0
60
+ ensure
53
61
  input.rewind
54
- body = env['api.request.input'] = input.read
55
- begin
56
- read_rack_input(body) if body && body.length > 0
57
- ensure
58
- input.rewind
59
- end
60
62
  end
61
63
  end
62
64
  end
65
+ end
63
66
 
64
- # store parsed input in env['api.request.body']
65
- def read_rack_input(body)
66
- fmt = mime_types[request.media_type] if request.media_type
67
- fmt ||= options[:default_format]
68
- if content_type_for(fmt)
69
- parser = Grape::Parser::Base.parser_for fmt, options
70
- if parser
71
- begin
72
- body = (env['api.request.body'] = parser.call(body, env))
73
- if body.is_a?(Hash)
74
- if env['rack.request.form_hash']
75
- env['rack.request.form_hash'] = env['rack.request.form_hash'].merge(body)
76
- else
77
- env['rack.request.form_hash'] = body
78
- end
79
- env['rack.request.form_input'] = env['rack.input']
67
+ # store parsed input in env['api.request.body']
68
+ def read_rack_input(body)
69
+ fmt = mime_types[request.media_type] if request.media_type
70
+ fmt ||= options[:default_format]
71
+ if content_type_for(fmt)
72
+ parser = Grape::Parser::Base.parser_for fmt, options
73
+ if parser
74
+ begin
75
+ body = (env['api.request.body'] = parser.call(body, env))
76
+ if body.is_a?(Hash)
77
+ if env['rack.request.form_hash']
78
+ env['rack.request.form_hash'] = env['rack.request.form_hash'].merge(body)
79
+ else
80
+ env['rack.request.form_hash'] = body
80
81
  end
81
- rescue StandardError => e
82
- throw :error, status: 400, message: e.message
82
+ env['rack.request.form_input'] = env['rack.input']
83
83
  end
84
- else
85
- env['api.request.body'] = body
84
+ rescue StandardError => e
85
+ throw :error, status: 400, message: e.message
86
86
  end
87
87
  else
88
- throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
88
+ env['api.request.body'] = body
89
89
  end
90
+ else
91
+ throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
90
92
  end
93
+ end
91
94
 
92
- def negotiate_content_type
93
- fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
94
- if content_type_for(fmt)
95
- env['api.format'] = fmt
96
- else
97
- throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
98
- end
95
+ def negotiate_content_type
96
+ fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
97
+ if content_type_for(fmt)
98
+ env['api.format'] = fmt
99
+ else
100
+ throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
99
101
  end
102
+ end
100
103
 
101
- def format_from_extension
102
- parts = request.path.split('.')
104
+ def format_from_extension
105
+ parts = request.path.split('.')
103
106
 
104
- if parts.size > 1
105
- extension = parts.last
106
- # avoid symbol memory leak on an unknown format
107
- return extension.to_sym if content_type_for(extension)
108
- end
109
- nil
110
- end
111
-
112
- def format_from_params
113
- fmt = Rack::Utils.parse_nested_query(env['QUERY_STRING'])["format"]
107
+ if parts.size > 1
108
+ extension = parts.last
114
109
  # avoid symbol memory leak on an unknown format
115
- return fmt.to_sym if content_type_for(fmt)
116
- fmt
110
+ return extension.to_sym if content_type_for(extension)
117
111
  end
112
+ nil
113
+ end
118
114
 
119
- def format_from_header
120
- mime_array.each do |t|
121
- return mime_types[t] if mime_types.key?(t)
122
- end
123
- nil
124
- end
115
+ def format_from_params
116
+ fmt = Rack::Utils.parse_nested_query(env['QUERY_STRING'])["format"]
117
+ # avoid symbol memory leak on an unknown format
118
+ return fmt.to_sym if content_type_for(fmt)
119
+ fmt
120
+ end
125
121
 
126
- def mime_array
127
- accept = headers['accept']
128
- return [] unless accept
122
+ def format_from_header
123
+ mime_array.each do |t|
124
+ return mime_types[t] if mime_types.key?(t)
125
+ end
126
+ nil
127
+ end
129
128
 
130
- accept_into_mime_and_quality = %r(
131
- (
132
- \w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
133
- (?:
134
- (?:;[^,]*?)? # optionally multiple formats in a row
135
- ;\s*q=([\d.]+) # optional "quality" preference (eg q=0.5)
136
- )?
137
- )x
129
+ def mime_array
130
+ accept = headers['accept']
131
+ return [] unless accept
138
132
 
139
- vendor_prefix_pattern = /vnd\.[^+]+\+/
133
+ accept_into_mime_and_quality = %r(
134
+ (
135
+ \w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
136
+ (?:
137
+ (?:;[^,]*?)? # optionally multiple formats in a row
138
+ ;\s*q=([\d.]+) # optional "quality" preference (eg q=0.5)
139
+ )?
140
+ )x
140
141
 
141
- accept.scan(accept_into_mime_and_quality)
142
- .sort_by { |_, quality_preference| -quality_preference.to_f }
143
- .map { |mime, _| mime.sub(vendor_prefix_pattern, '') }
144
- end
142
+ vendor_prefix_pattern = /vnd\.[^+]+\+/
145
143
 
144
+ accept.scan(accept_into_mime_and_quality)
145
+ .sort_by { |_, quality_preference| -quality_preference.to_f }
146
+ .map { |mime, _| mime.sub(vendor_prefix_pattern, '') }
147
+ end
146
148
  end
147
149
  end
148
150
  end
@@ -0,0 +1,13 @@
1
+ require 'grape/middleware/base'
2
+
3
+ module Grape
4
+ module Middleware
5
+ class Globals < Base
6
+ def before
7
+ @env['grape.request'] = Grape::Request.new(@env)
8
+ @env['grape.request.headers'] = request.headers
9
+ @env['grape.request.params'] = request.params if @env['rack.input']
10
+ end
11
+ end
12
+ end
13
+ end
@@ -17,7 +17,6 @@ module Grape
17
17
  # X-Cascade header to alert Rack::Mount to attempt the next matched
18
18
  # route.
19
19
  class AcceptVersionHeader < Base
20
-
21
20
  def before
22
21
  potential_version = (env['HTTP_ACCEPT_VERSION'] || '').strip
23
22
 
@@ -62,7 +61,6 @@ module Grape
62
61
  def error_headers
63
62
  cascade? ? { 'X-Cascade' => 'pass' } : {}
64
63
  end
65
-
66
64
  end
67
65
  end
68
66
  end
@@ -22,9 +22,12 @@ module Grape
22
22
  # X-Cascade header to alert Rack::Mount to attempt the next matched
23
23
  # route.
24
24
  class Header < Base
25
-
26
25
  def before
27
- header = Rack::Accept::MediaType.new env['HTTP_ACCEPT']
26
+ begin
27
+ header = Rack::Accept::MediaType.new env['HTTP_ACCEPT']
28
+ rescue RuntimeError => e
29
+ throw :error, status: 406, headers: error_headers, message: e.message
30
+ end
28
31
 
29
32
  if strict?
30
33
  # If no Accept header:
@@ -123,7 +126,6 @@ module Grape
123
126
  _, subtype = Rack::Accept::Header.parse_media_type media_type
124
127
  subtype[/\Avnd\.[a-z0-9*.]+-[a-z0-9*\-.]+/]
125
128
  end
126
-
127
129
  end
128
130
  end
129
131
  end
@@ -27,17 +27,15 @@ module Grape
27
27
 
28
28
  def before
29
29
  paramkey = options[:parameter]
30
- potential_version = request.params[paramkey]
31
-
30
+ potential_version = Rack::Utils.parse_nested_query(env['QUERY_STRING'])[paramkey]
32
31
  unless potential_version.nil?
33
32
  if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
34
33
  throw :error, status: 404, message: "404 API Version Not Found", headers: { 'X-Cascade' => 'pass' }
35
34
  end
36
35
  env['api.version'] = potential_version
37
- env['rack.request.query_hash'].delete(paramkey)
36
+ env['rack.request.query_hash'].delete(paramkey) if env.key? 'rack.request.query_hash'
38
37
  end
39
38
  end
40
-
41
39
  end
42
40
  end
43
41
  end
@@ -43,10 +43,9 @@ module Grape
43
43
 
44
44
  private
45
45
 
46
- def prefix
47
- Rack::Mount::Utils.normalize_path(options[:prefix].to_s) if options[:prefix]
48
- end
49
-
46
+ def prefix
47
+ Rack::Mount::Utils.normalize_path(options[:prefix].to_s) if options[:prefix]
48
+ end
50
49
  end
51
50
  end
52
51
  end
@@ -19,6 +19,5 @@ module Grape
19
19
  def self.joined_space_path(settings)
20
20
  Rack::Mount::Utils.normalize_path(joined_space(settings))
21
21
  end
22
-
23
22
  end
24
23
  end
@@ -1,9 +1,7 @@
1
1
  module Grape
2
2
  module Parser
3
3
  module Base
4
-
5
4
  class << self
6
-
7
5
  PARSERS = {
8
6
  json: Grape::Parser::Json,
9
7
  jsonapi: Grape::Parser::Json,
@@ -25,7 +23,6 @@ module Grape
25
23
  spec
26
24
  end
27
25
  end
28
-
29
26
  end
30
27
  end
31
28
  end
@@ -2,11 +2,9 @@ module Grape
2
2
  module Parser
3
3
  module Json
4
4
  class << self
5
-
6
5
  def call(object, env)
7
6
  MultiJson.load(object)
8
7
  end
9
-
10
8
  end
11
9
  end
12
10
  end
@@ -2,11 +2,9 @@ module Grape
2
2
  module Parser
3
3
  module Xml
4
4
  class << self
5
-
6
5
  def call(object, env)
7
6
  MultiXml.parse(object)
8
7
  end
9
-
10
8
  end
11
9
  end
12
10
  end
@@ -1,6 +1,5 @@
1
1
  module Grape
2
2
  class Path
3
-
4
3
  def self.prepare(raw_path, namespace, settings)
5
4
  Path.new(raw_path, namespace, settings).path_with_suffix
6
5
  end
@@ -22,7 +21,7 @@ module Grape
22
21
  end
23
22
 
24
23
  def uses_path_versioning?
25
- settings[:version] && settings[:version_options][:using] == :path
24
+ !!(settings[:version] && settings[:version_options][:using] == :path)
26
25
  end
27
26
 
28
27
  def has_namespace?
@@ -67,6 +66,5 @@ module Grape
67
66
  return if settings[key].nil?
68
67
  settings[key].to_s.split("/")
69
68
  end
70
-
71
69
  end
72
70
  end
@@ -1,8 +1,6 @@
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
@@ -25,6 +23,5 @@ module Grape
25
23
  def to_ary
26
24
  nil
27
25
  end
28
-
29
26
  end
30
27
  end
@@ -100,7 +100,7 @@ module Grape
100
100
  # @param key [Symbol] The key to gather
101
101
  # @return [Array]
102
102
  def gather(key)
103
- stack.map { |s| s[key] }.flatten.compact.uniq
103
+ stack.flat_map { |s| s[key] }.compact.uniq
104
104
  end
105
105
 
106
106
  def to_s
@@ -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
  #
@@ -64,7 +62,6 @@ module Grape
64
62
  @option = options
65
63
  super
66
64
  end
67
-
68
65
  end
69
66
 
70
67
  # We define Validator::inherited here so SingleOptionValidator
@@ -94,6 +91,7 @@ module Grape
94
91
  @parent = opts[:parent]
95
92
  @api = opts[:api]
96
93
  @optional = opts[:optional] || false
94
+ @type = opts[:type]
97
95
  @declared_params = []
98
96
 
99
97
  instance_eval(&block)
@@ -102,41 +100,68 @@ module Grape
102
100
  end
103
101
 
104
102
  def should_validate?(parameters)
105
- return false if @optional && params(parameters).blank?
103
+ return false if @optional && params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
106
104
  return true if parent.nil?
107
105
  parent.should_validate?(parameters)
108
106
  end
109
107
 
110
108
  def requires(*attrs, &block)
111
- return new_scope(attrs, &block) if block_given?
109
+ orig_attrs = attrs.clone
112
110
 
113
- validations = { presence: true }
114
- validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
111
+ opts = attrs.last.is_a?(Hash) ? attrs.pop : nil
115
112
 
116
- push_declared_params(attrs)
117
- validates(attrs, validations)
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)
120
+ end
118
121
  end
119
122
 
120
123
  def optional(*attrs, &block)
121
- return new_scope(attrs, true, &block) if block_given?
124
+ orig_attrs = attrs
122
125
 
123
126
  validations = {}
124
127
  validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
125
-
126
- push_declared_params(attrs)
128
+ validations[:type] ||= Array if block_given?
127
129
  validates(attrs, validations)
130
+
131
+ block_given? ? new_scope(orig_attrs, true, &block) :
132
+ push_declared_params(attrs)
128
133
  end
129
134
 
130
- def group(element, &block)
131
- requires(element, &block)
135
+ def group(*attrs, &block)
136
+ requires(*attrs, &block)
132
137
  end
133
138
 
134
139
  def params(params)
135
140
  params = @parent.params(params) if @parent
136
- params = params[@element] || {} if @element
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
137
150
  params
138
151
  end
139
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(&params_block)
160
+ end
161
+ end
162
+ alias_method :use_scope, :use
163
+ alias_method :includes, :use
164
+
140
165
  def full_name(name)
141
166
  return "#{@parent.full_name(@element)}[#{name}]" if @parent
142
167
  name.to_s
@@ -150,9 +175,33 @@ module Grape
150
175
 
151
176
  private
152
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
+
153
201
  def new_scope(attrs, optional = false, &block)
154
- raise ArgumentError unless attrs.size == 1
155
- ParamsScope.new(api: @api, element: attrs.first, parent: self, optional: optional, &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)
156
205
  end
157
206
 
158
207
  # Pushes declared params to parent or settings
@@ -183,16 +232,20 @@ module Grape
183
232
  values = validations[:values]
184
233
  doc_attrs[:values] = values if values
185
234
 
235
+ values = (values.is_a?(Proc) ? values.call : values)
236
+
186
237
  # default value should be present in values array, if both exist
187
238
  if default && values && !values.include?(default)
188
239
  raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
189
240
  end
190
241
 
191
242
  # type should be compatible with values array, if both exist
192
- if coerce_type && values && values.any? { |v| !v.instance_of?(coerce_type) }
243
+ if coerce_type && values && values.any? { |v| !v.kind_of?(coerce_type) }
193
244
  raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
194
245
  end
195
246
 
247
+ doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
248
+
196
249
  full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
197
250
  @api.document_attribute(full_attrs, doc_attrs)
198
251
 
@@ -224,7 +277,6 @@ module Grape
224
277
  raise Grape::Exceptions::UnknownValidator.new(type)
225
278
  end
226
279
  end
227
-
228
280
  end
229
281
 
230
282
  # This module is mixed into the API Class.
@@ -235,7 +287,7 @@ module Grape
235
287
  end
236
288
 
237
289
  def params(&block)
238
- ParamsScope.new(api: self, &block)
290
+ ParamsScope.new(api: self, type: Hash, &block)
239
291
  end
240
292
 
241
293
  def document_attribute(names, opts)
@@ -246,9 +298,7 @@ module Grape
246
298
  @last_description[:params][name[:full_name].to_s].merge!(opts)
247
299
  end
248
300
  end
249
-
250
301
  end
251
-
252
302
  end
253
303
  end
254
304