grape 0.11.0 → 0.12.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +23 -80
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +27 -0
  5. data/Gemfile +1 -1
  6. data/Guardfile +1 -1
  7. data/LICENSE +1 -1
  8. data/README.md +131 -30
  9. data/Rakefile +1 -1
  10. data/UPGRADING.md +110 -1
  11. data/gemfiles/rails_3.gemfile +1 -1
  12. data/gemfiles/rails_4.gemfile +1 -1
  13. data/grape.gemspec +4 -4
  14. data/lib/grape.rb +92 -62
  15. data/lib/grape/api.rb +10 -10
  16. data/lib/grape/cookies.rb +1 -1
  17. data/lib/grape/dsl/configuration.rb +7 -7
  18. data/lib/grape/dsl/helpers.rb +3 -3
  19. data/lib/grape/dsl/inside_route.rb +50 -21
  20. data/lib/grape/dsl/parameters.rb +25 -6
  21. data/lib/grape/dsl/request_response.rb +1 -1
  22. data/lib/grape/dsl/routing.rb +11 -10
  23. data/lib/grape/dsl/settings.rb +1 -1
  24. data/lib/grape/endpoint.rb +21 -19
  25. data/lib/grape/error_formatter/json.rb +1 -1
  26. data/lib/grape/exceptions/base.rb +1 -1
  27. data/lib/grape/exceptions/validation.rb +1 -1
  28. data/lib/grape/exceptions/validation_errors.rb +2 -2
  29. data/lib/grape/formatter/base.rb +1 -1
  30. data/lib/grape/formatter/json.rb +1 -1
  31. data/lib/grape/formatter/serializable_hash.rb +4 -4
  32. data/lib/grape/formatter/txt.rb +1 -1
  33. data/lib/grape/formatter/xml.rb +1 -1
  34. data/lib/grape/http/headers.rb +27 -0
  35. data/lib/grape/http/request.rb +1 -1
  36. data/lib/grape/middleware/error.rb +10 -4
  37. data/lib/grape/middleware/formatter.rb +13 -9
  38. data/lib/grape/middleware/globals.rb +2 -1
  39. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -2
  40. data/lib/grape/middleware/versioner/header.rb +4 -4
  41. data/lib/grape/middleware/versioner/param.rb +2 -2
  42. data/lib/grape/middleware/versioner/path.rb +1 -1
  43. data/lib/grape/namespace.rb +2 -1
  44. data/lib/grape/parser/json.rb +1 -1
  45. data/lib/grape/parser/xml.rb +1 -1
  46. data/lib/grape/path.rb +3 -3
  47. data/lib/grape/presenters/presenter.rb +9 -0
  48. data/lib/grape/validations/params_scope.rb +3 -3
  49. data/lib/grape/validations/validators/allow_blank.rb +1 -1
  50. data/lib/grape/validations/validators/coerce.rb +6 -5
  51. data/lib/grape/validations/validators/default.rb +2 -2
  52. data/lib/grape/validations/validators/multiple_params_base.rb +1 -0
  53. data/lib/grape/validations/validators/regexp.rb +1 -1
  54. data/lib/grape/version.rb +1 -1
  55. data/spec/grape/api/custom_validations_spec.rb +47 -0
  56. data/spec/grape/api/deeply_included_options_spec.rb +56 -0
  57. data/spec/grape/api_spec.rb +64 -42
  58. data/spec/grape/dsl/configuration_spec.rb +2 -2
  59. data/spec/grape/dsl/helpers_spec.rb +1 -1
  60. data/spec/grape/dsl/inside_route_spec.rb +75 -19
  61. data/spec/grape/dsl/parameters_spec.rb +59 -10
  62. data/spec/grape/dsl/request_response_spec.rb +62 -2
  63. data/spec/grape/dsl/routing_spec.rb +116 -18
  64. data/spec/grape/endpoint_spec.rb +57 -5
  65. data/spec/grape/entity_spec.rb +1 -1
  66. data/spec/grape/exceptions/body_parse_errors_spec.rb +5 -5
  67. data/spec/grape/exceptions/invalid_accept_header_spec.rb +32 -32
  68. data/spec/grape/exceptions/validation_errors_spec.rb +1 -1
  69. data/spec/grape/integration/rack_spec.rb +4 -3
  70. data/spec/grape/middleware/auth/strategies_spec.rb +2 -2
  71. data/spec/grape/middleware/base_spec.rb +2 -2
  72. data/spec/grape/middleware/error_spec.rb +1 -1
  73. data/spec/grape/middleware/exception_spec.rb +5 -5
  74. data/spec/grape/middleware/formatter_spec.rb +10 -10
  75. data/spec/grape/middleware/globals_spec.rb +27 -0
  76. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +1 -1
  77. data/spec/grape/middleware/versioner/header_spec.rb +1 -1
  78. data/spec/grape/middleware/versioner/param_spec.rb +1 -1
  79. data/spec/grape/middleware/versioner/path_spec.rb +1 -1
  80. data/spec/grape/path_spec.rb +6 -4
  81. data/spec/grape/presenters/presenter_spec.rb +70 -0
  82. data/spec/grape/util/inheritable_values_spec.rb +1 -1
  83. data/spec/grape/util/stackable_values_spec.rb +1 -1
  84. data/spec/grape/util/strict_hash_configuration_spec.rb +1 -1
  85. data/spec/grape/validations/params_scope_spec.rb +64 -0
  86. data/spec/grape/validations/validators/allow_blank_spec.rb +10 -0
  87. data/spec/grape/validations/validators/coerce_spec.rb +48 -18
  88. data/spec/grape/validations/validators/default_spec.rb +110 -20
  89. data/spec/grape/validations/validators/presence_spec.rb +41 -3
  90. data/spec/grape/validations/validators/regexp_spec.rb +7 -2
  91. data/spec/grape/validations_spec.rb +20 -1
  92. data/spec/support/file_streamer.rb +11 -0
  93. data/spec/support/versioned_helpers.rb +1 -1
  94. metadata +14 -2
@@ -48,13 +48,13 @@ module Grape
48
48
  def desc_container
49
49
  Module.new do
50
50
  include Grape::Util::StrictHashConfiguration.module(
51
- :description,
52
- :detail,
53
- :params,
54
- :entity,
55
- :http_codes,
56
- :named,
57
- :headers
51
+ :description,
52
+ :detail,
53
+ :params,
54
+ :entity,
55
+ :http_codes,
56
+ :named,
57
+ :headers
58
58
  )
59
59
 
60
60
  def config_context.success(*args)
@@ -51,7 +51,7 @@ module Grape
51
51
 
52
52
  protected
53
53
 
54
- def inject_api_helpers_to_mod(mod, &block)
54
+ def inject_api_helpers_to_mod(mod, &_block)
55
55
  mod.extend(BaseHelper)
56
56
  yield if block_given?
57
57
  mod.api_changed(self)
@@ -59,12 +59,12 @@ module Grape
59
59
  end
60
60
 
61
61
  # This module extends user defined helpers
62
- # to provide some API-specific functionality
62
+ # to provide some API-specific functionality.
63
63
  module BaseHelper
64
64
  attr_accessor :api
65
65
  def params(name, &block)
66
66
  @named_params ||= {}
67
- @named_params.merge! name => block
67
+ @named_params[name] = block
68
68
  end
69
69
 
70
70
  def api_changed(new_api)
@@ -9,12 +9,12 @@ module Grape
9
9
  # A filtering method that will return a hash
10
10
  # consisting only of keys that have been declared by a
11
11
  # `params` statement against the current/target endpoint or parent
12
- # namespaces
12
+ # namespaces.
13
13
  #
14
14
  # @param params [Hash] The initial hash to filter. Usually this will just be `params`
15
15
  # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
16
16
  # options. `:include_parent_namespaces` defaults to true, hence must be set to false if
17
- # you want only to return params declared against the current/target endpoint
17
+ # you want only to return params declared against the current/target endpoint.
18
18
  def declared(params, options = {}, declared_params = nil)
19
19
  options[:include_missing] = true unless options.key?(:include_missing)
20
20
  options[:include_parent_namespaces] = true unless options.key?(:include_parent_namespaces)
@@ -39,15 +39,14 @@ module Grape
39
39
  key.each_pair do |parent, children|
40
40
  output_key = options[:stringify] ? parent.to_s : parent.to_sym
41
41
 
42
- next unless options[:include_missing] || children || params.key?(parent)
42
+ next unless options[:include_missing] || params.key?(parent)
43
43
 
44
- if params.key?(parent) || options[:include_missing]
45
- hash[output_key] = if children
46
- declared(params[parent] || {}, options, Array(children))
47
- else
48
- params[parent]
49
- end
50
- end
44
+ hash[output_key] = if children
45
+ children_params = params[parent] || (children.is_a?(Array) ? [] : {})
46
+ declared(children_params, options, Array(children))
47
+ else
48
+ params[parent]
49
+ end
51
50
  end
52
51
 
53
52
  hash
@@ -80,7 +79,7 @@ module Grape
80
79
  if merged_options[:permanent]
81
80
  status 301
82
81
  else
83
- if env['HTTP_VERSION'] == 'HTTP/1.1' && request.request_method.to_s.upcase != 'GET'
82
+ if env[Grape::Http::Headers::HTTP_VERSION] == 'HTTP/1.1' && request.request_method.to_s.upcase != Grape::Http::Headers::GET
84
83
  status 303
85
84
  else
86
85
  status 302
@@ -94,16 +93,25 @@ module Grape
94
93
  #
95
94
  # @param status [Integer] The HTTP Status Code to return for this request.
96
95
  def status(status = nil)
97
- if status
96
+ case status
97
+ when Symbol
98
+ if Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.include?(status)
99
+ @status = Rack::Utils.status_code(status)
100
+ else
101
+ fail ArgumentError, "Status code :#{status} is invalid."
102
+ end
103
+ when Fixnum
98
104
  @status = status
99
- else
105
+ when nil
100
106
  return @status if @status
101
107
  case request.request_method.to_s.upcase
102
- when 'POST'
108
+ when Grape::Http::Headers::POST
103
109
  201
104
110
  else
105
111
  200
106
112
  end
113
+ else
114
+ fail ArgumentError, 'Status code must be Fixnum or Symbol.'
107
115
  end
108
116
  end
109
117
 
@@ -120,9 +128,9 @@ module Grape
120
128
  # Set response content-type
121
129
  def content_type(val = nil)
122
130
  if val
123
- header('Content-Type', val)
131
+ header(Grape::Http::Headers::CONTENT_TYPE, val)
124
132
  else
125
- header['Content-Type']
133
+ header[Grape::Http::Headers::CONTENT_TYPE]
126
134
  end
127
135
  end
128
136
 
@@ -159,6 +167,22 @@ module Grape
159
167
  end
160
168
  end
161
169
 
170
+ # Allows you to define the response as a file-like object.
171
+ #
172
+ # @example
173
+ # get '/file' do
174
+ # file FileStreamer.new(...)
175
+ # end
176
+ #
177
+ # GET /file # => "contents of file"
178
+ def file(value = nil)
179
+ if value
180
+ @file = value
181
+ else
182
+ @file
183
+ end
184
+ end
185
+
162
186
  # Allows you to make use of Grape Entities by setting
163
187
  # the response body to the serializable hash of the
164
188
  # entity provided in the `:with` option. This has the
@@ -186,9 +210,7 @@ module Grape
186
210
  root = options.delete(:root)
187
211
 
188
212
  representation = if entity_class
189
- embeds = { env: env }
190
- embeds[:version] = env['api.version'] if env['api.version']
191
- entity_class.represent(object, embeds.merge(options))
213
+ entity_representation_for(entity_class, object, options)
192
214
  else
193
215
  object
194
216
  end
@@ -196,8 +218,9 @@ module Grape
196
218
  representation = { root => representation } if root
197
219
  if key
198
220
  representation = (@body || {}).merge(key => representation)
199
- elsif entity_class.present? && representation.respond_to?('merge')
200
- representation = (@body || {}).merge(representation)
221
+ elsif entity_class.present? && @body
222
+ fail ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?('merge')
223
+ representation = @body.merge(representation)
201
224
  end
202
225
 
203
226
  body representation
@@ -235,6 +258,12 @@ module Grape
235
258
 
236
259
  entity_class
237
260
  end
261
+
262
+ def entity_representation_for(entity_class, object, options)
263
+ embeds = { env: env }
264
+ embeds[:version] = env['api.version'] if env['api.version']
265
+ entity_class.represent(object, embeds.merge(options))
266
+ end
238
267
  end
239
268
  end
240
269
  end
@@ -5,6 +5,27 @@ module Grape
5
5
  module Parameters
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ # Include reusable params rules among current.
9
+ # You can define reusable params with helpers method.
10
+ #
11
+ # @example
12
+ #
13
+ # class API < Grape::API
14
+ # helpers do
15
+ # params :pagination do
16
+ # optional :page, type: Integer
17
+ # optional :per_page, type: Integer
18
+ # end
19
+ # end
20
+ #
21
+ # desc "Get collection"
22
+ # params do
23
+ # use :pagination
24
+ # end
25
+ # get do
26
+ # Collection.page(params[:page]).per(params[:per_page])
27
+ # end
28
+ # end
8
29
  def use(*names)
9
30
  named_params = Grape::DSL::Configuration.stacked_hash_to_hash(@api.namespace_stackable(:named_params)) || {}
10
31
  options = names.last.is_a?(Hash) ? names.pop : {}
@@ -21,8 +42,8 @@ module Grape
21
42
  def requires(*attrs, &block)
22
43
  orig_attrs = attrs.clone
23
44
 
24
- opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
25
- opts.merge!(presence: true)
45
+ opts = attrs.last.is_a?(Hash) ? attrs.pop.clone : {}
46
+ opts[:presence] = true
26
47
 
27
48
  if opts[:using]
28
49
  require_required_and_optional_fields(attrs.first, opts)
@@ -37,7 +58,7 @@ module Grape
37
58
  def optional(*attrs, &block)
38
59
  orig_attrs = attrs.clone
39
60
 
40
- opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
61
+ opts = attrs.last.is_a?(Hash) ? attrs.pop.clone : {}
41
62
  type = opts[:type]
42
63
 
43
64
  # check type for optional parameter group
@@ -72,9 +93,7 @@ module Grape
72
93
  validates(attrs, all_or_none_of: true)
73
94
  end
74
95
 
75
- def group(*attrs, &block)
76
- requires(*attrs, &block)
77
- end
96
+ alias_method :group, :requires
78
97
 
79
98
  def params(params)
80
99
  params = @parent.params(params) if @parent
@@ -96,7 +96,7 @@ module Grape
96
96
  # @option options [Boolean] :backtrace Include a backtrace in the rescue response.
97
97
  # @option options [Boolean] :rescue_subclasses Also rescue subclasses of exception classes
98
98
  # @param [Proc] handler Execution proc to handle the given exception as an
99
- # alternative to passing a block
99
+ # alternative to passing a block.
100
100
  def rescue_from(*args, &block)
101
101
  if args.last.is_a?(Proc)
102
102
  handler = args.pop
@@ -59,12 +59,12 @@ module Grape
59
59
  namespace_inheritable(:root_prefix, prefix)
60
60
  end
61
61
 
62
- # Do not route HEAD requests to GET requests automatically
62
+ # Do not route HEAD requests to GET requests automatically.
63
63
  def do_not_route_head!
64
64
  namespace_inheritable(:do_not_route_head, true)
65
65
  end
66
66
 
67
- # Do not automatically route OPTIONS
67
+ # Do not automatically route OPTIONS.
68
68
  def do_not_route_options!
69
69
  namespace_inheritable(:do_not_route_options, true)
70
70
  end
@@ -89,11 +89,11 @@ module Grape
89
89
  end
90
90
 
91
91
  endpoints << Grape::Endpoint.new(
92
- in_setting,
93
- method: :any,
94
- path: path,
95
- app: app,
96
- for: self
92
+ in_setting,
93
+ method: :any,
94
+ path: path,
95
+ app: app,
96
+ for: self
97
97
  )
98
98
  end
99
99
  end
@@ -120,13 +120,14 @@ module Grape
120
120
  }).deep_merge(route_setting(:description) || {}).deep_merge(route_options || {})
121
121
  }
122
122
 
123
- endpoints << Grape::Endpoint.new(inheritable_setting, endpoint_options, &block)
123
+ new_endpoint = Grape::Endpoint.new(inheritable_setting, endpoint_options, &block)
124
+ endpoints << new_endpoint unless endpoints.any? { |e| e.equals?(new_endpoint) }
124
125
 
125
126
  route_end
126
127
  reset_validations!
127
128
  end
128
129
 
129
- %w"get post put head delete options patch".each do |meth|
130
+ %w(get post put head delete options patch).each do |meth|
130
131
  define_method meth do |*args, &block|
131
132
  options = args.extract_options!
132
133
  paths = args.first || ['/']
@@ -134,7 +135,7 @@ module Grape
134
135
  end
135
136
  end
136
137
 
137
- def namespace(space = nil, options = {}, &block)
138
+ def namespace(space = nil, options = {}, &block)
138
139
  if space || block_given?
139
140
  within_namespace do
140
141
  previous_namespace_description = @namespace_description
@@ -95,7 +95,7 @@ module Grape
95
95
  reset_validations!
96
96
  end
97
97
 
98
- def within_namespace(&block)
98
+ def within_namespace(&_block)
99
99
  namespace_start
100
100
 
101
101
  result = yield if block_given?
@@ -83,7 +83,7 @@ module Grape
83
83
  Namespace.joined_space(namespace_stackable(:namespace)),
84
84
  (namespace_stackable(:mount_path) || []).join('/'),
85
85
  options[:path].join('/')
86
- ].join(' ')
86
+ ].join(' ')
87
87
  end
88
88
 
89
89
  def routes
@@ -106,8 +106,8 @@ module Grape
106
106
 
107
107
  routes.each do |route|
108
108
  methods = [route.route_method]
109
- if !namespace_inheritable(:do_not_route_head) && route.route_method == 'GET'
110
- methods << 'HEAD'
109
+ if !namespace_inheritable(:do_not_route_head) && route.route_method == Grape::Http::Headers::GET
110
+ methods << Grape::Http::Headers::HEAD
111
111
  end
112
112
  methods.each do |method|
113
113
  route_set.add_route(self, {
@@ -151,14 +151,14 @@ module Grape
151
151
  request_method = (method.to_s.upcase unless method == :any)
152
152
 
153
153
  Route.new(options[:route_options].clone.merge(
154
- prefix: namespace_inheritable(:root_prefix),
155
- version: namespace_inheritable(:version) ? namespace_inheritable(:version).join('|') : nil,
156
- namespace: namespace,
157
- method: request_method,
158
- path: prepared_path,
159
- params: prepare_routes_path_params(path),
160
- compiled: path,
161
- settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations)
154
+ prefix: namespace_inheritable(:root_prefix),
155
+ version: namespace_inheritable(:version) ? namespace_inheritable(:version).join('|') : nil,
156
+ namespace: namespace,
157
+ method: request_method,
158
+ path: prepared_path,
159
+ params: prepare_routes_path_params(path),
160
+ compiled: path,
161
+ settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations)
162
162
  ))
163
163
  end
164
164
  end.flatten
@@ -192,7 +192,7 @@ module Grape
192
192
  options[:app].call(env)
193
193
  else
194
194
  builder = build_middleware
195
- builder.run lambda { |arg| run(arg) }
195
+ builder.run ->(arg) { run(arg) }
196
196
  builder.call(env)
197
197
  end
198
198
  end
@@ -200,11 +200,11 @@ module Grape
200
200
  # Return the collection of endpoints within this endpoint.
201
201
  # This is the case when an Grape::API mounts another Grape::API.
202
202
  def endpoints
203
- if options[:app] && options[:app].respond_to?(:endpoints)
204
- options[:app].endpoints
205
- else
206
- nil
207
- end
203
+ options[:app].endpoints if options[:app] && options[:app].respond_to?(:endpoints)
204
+ end
205
+
206
+ def equals?(e)
207
+ (options == e.options) && (inheritable_setting.to_hash == e.inheritable_setting.to_hash)
208
208
  end
209
209
 
210
210
  protected
@@ -244,11 +244,13 @@ module Grape
244
244
 
245
245
  run_filters after_validations
246
246
 
247
- response_text = @block ? @block.call(self) : nil
247
+ response_object = @block ? @block.call(self) : nil
248
248
  run_filters afters
249
249
  cookies.write(header)
250
250
 
251
- [status, header, [body || response_text]]
251
+ # The Body commonly is an Array of Strings, the application instance itself, or a File-like object.
252
+ response_object = file || [body || response_object]
253
+ [status, header, response_object]
252
254
  end
253
255
 
254
256
  def build_middleware
@@ -5,7 +5,7 @@ module Grape
5
5
  def call(message, backtrace, options = {}, env = nil)
6
6
  message = Grape::ErrorFormatter::Base.present(message, env)
7
7
 
8
- result = message.is_a?(Hash) ? message : { error: message }
8
+ result = message.is_a?(String) ? { error: message } : message
9
9
  if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
10
10
  result = result.merge(backtrace: backtrace)
11
11
  end
@@ -29,7 +29,7 @@ module Grape
29
29
  @summary = summary(key, attributes)
30
30
  @resolution = resolution(key, attributes)
31
31
  [['Problem', @problem], ['Summary', @summary], ['Resolution', @resolution]].reduce('') do |message, detail_array|
32
- message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank?
32
+ message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank?
33
33
  message
34
34
  end
35
35
  else
@@ -14,7 +14,7 @@ module Grape
14
14
 
15
15
  # remove all the unnecessary stuff from Grape::Exceptions::Base like status
16
16
  # and headers when converting a validation error to json or string
17
- def as_json(*args)
17
+ def as_json(*_args)
18
18
  to_s
19
19
  end
20
20
 
@@ -24,7 +24,7 @@ module Grape
24
24
  end
25
25
  end
26
26
 
27
- def as_json
27
+ def as_json(_opts = {})
28
28
  errors.map do |k, v|
29
29
  {
30
30
  params: k,
@@ -33,7 +33,7 @@ module Grape
33
33
  end
34
34
  end
35
35
 
36
- def to_json
36
+ def to_json(_opts = {})
37
37
  as_json.to_json
38
38
  end
39
39