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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +23 -80
- data/.travis.yml +1 -1
- data/CHANGELOG.md +27 -0
- data/Gemfile +1 -1
- data/Guardfile +1 -1
- data/LICENSE +1 -1
- data/README.md +131 -30
- data/Rakefile +1 -1
- data/UPGRADING.md +110 -1
- data/gemfiles/rails_3.gemfile +1 -1
- data/gemfiles/rails_4.gemfile +1 -1
- data/grape.gemspec +4 -4
- data/lib/grape.rb +92 -62
- data/lib/grape/api.rb +10 -10
- data/lib/grape/cookies.rb +1 -1
- data/lib/grape/dsl/configuration.rb +7 -7
- data/lib/grape/dsl/helpers.rb +3 -3
- data/lib/grape/dsl/inside_route.rb +50 -21
- data/lib/grape/dsl/parameters.rb +25 -6
- data/lib/grape/dsl/request_response.rb +1 -1
- data/lib/grape/dsl/routing.rb +11 -10
- data/lib/grape/dsl/settings.rb +1 -1
- data/lib/grape/endpoint.rb +21 -19
- data/lib/grape/error_formatter/json.rb +1 -1
- data/lib/grape/exceptions/base.rb +1 -1
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +2 -2
- data/lib/grape/formatter/base.rb +1 -1
- data/lib/grape/formatter/json.rb +1 -1
- data/lib/grape/formatter/serializable_hash.rb +4 -4
- data/lib/grape/formatter/txt.rb +1 -1
- data/lib/grape/formatter/xml.rb +1 -1
- data/lib/grape/http/headers.rb +27 -0
- data/lib/grape/http/request.rb +1 -1
- data/lib/grape/middleware/error.rb +10 -4
- data/lib/grape/middleware/formatter.rb +13 -9
- data/lib/grape/middleware/globals.rb +2 -1
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -2
- data/lib/grape/middleware/versioner/header.rb +4 -4
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +2 -1
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +3 -3
- data/lib/grape/presenters/presenter.rb +9 -0
- data/lib/grape/validations/params_scope.rb +3 -3
- data/lib/grape/validations/validators/allow_blank.rb +1 -1
- data/lib/grape/validations/validators/coerce.rb +6 -5
- data/lib/grape/validations/validators/default.rb +2 -2
- data/lib/grape/validations/validators/multiple_params_base.rb +1 -0
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/custom_validations_spec.rb +47 -0
- data/spec/grape/api/deeply_included_options_spec.rb +56 -0
- data/spec/grape/api_spec.rb +64 -42
- data/spec/grape/dsl/configuration_spec.rb +2 -2
- data/spec/grape/dsl/helpers_spec.rb +1 -1
- data/spec/grape/dsl/inside_route_spec.rb +75 -19
- data/spec/grape/dsl/parameters_spec.rb +59 -10
- data/spec/grape/dsl/request_response_spec.rb +62 -2
- data/spec/grape/dsl/routing_spec.rb +116 -18
- data/spec/grape/endpoint_spec.rb +57 -5
- data/spec/grape/entity_spec.rb +1 -1
- data/spec/grape/exceptions/body_parse_errors_spec.rb +5 -5
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +32 -32
- data/spec/grape/exceptions/validation_errors_spec.rb +1 -1
- data/spec/grape/integration/rack_spec.rb +4 -3
- data/spec/grape/middleware/auth/strategies_spec.rb +2 -2
- data/spec/grape/middleware/base_spec.rb +2 -2
- data/spec/grape/middleware/error_spec.rb +1 -1
- data/spec/grape/middleware/exception_spec.rb +5 -5
- data/spec/grape/middleware/formatter_spec.rb +10 -10
- data/spec/grape/middleware/globals_spec.rb +27 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +1 -1
- data/spec/grape/middleware/versioner/header_spec.rb +1 -1
- data/spec/grape/middleware/versioner/param_spec.rb +1 -1
- data/spec/grape/middleware/versioner/path_spec.rb +1 -1
- data/spec/grape/path_spec.rb +6 -4
- data/spec/grape/presenters/presenter_spec.rb +70 -0
- data/spec/grape/util/inheritable_values_spec.rb +1 -1
- data/spec/grape/util/stackable_values_spec.rb +1 -1
- data/spec/grape/util/strict_hash_configuration_spec.rb +1 -1
- data/spec/grape/validations/params_scope_spec.rb +64 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +10 -0
- data/spec/grape/validations/validators/coerce_spec.rb +48 -18
- data/spec/grape/validations/validators/default_spec.rb +110 -20
- data/spec/grape/validations/validators/presence_spec.rb +41 -3
- data/spec/grape/validations/validators/regexp_spec.rb +7 -2
- data/spec/grape/validations_spec.rb +20 -1
- data/spec/support/file_streamer.rb +11 -0
- data/spec/support/versioned_helpers.rb +1 -1
- 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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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)
|
data/lib/grape/dsl/helpers.rb
CHANGED
@@ -51,7 +51,7 @@ module Grape
|
|
51
51
|
|
52
52
|
protected
|
53
53
|
|
54
|
-
def inject_api_helpers_to_mod(mod, &
|
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
|
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] ||
|
42
|
+
next unless options[:include_missing] || params.key?(parent)
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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[
|
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
|
-
|
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
|
-
|
105
|
+
when nil
|
100
106
|
return @status if @status
|
101
107
|
case request.request_method.to_s.upcase
|
102
|
-
when
|
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(
|
131
|
+
header(Grape::Http::Headers::CONTENT_TYPE, val)
|
124
132
|
else
|
125
|
-
header[
|
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
|
-
|
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? &&
|
200
|
-
|
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
|
data/lib/grape/dsl/parameters.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
data/lib/grape/dsl/routing.rb
CHANGED
@@ -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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
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
|
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 = {},
|
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
|
data/lib/grape/dsl/settings.rb
CHANGED
data/lib/grape/endpoint.rb
CHANGED
@@ -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
|
-
|
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 ==
|
110
|
-
methods <<
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
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
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
-
|
247
|
+
response_object = @block ? @block.call(self) : nil
|
248
248
|
run_filters afters
|
249
249
|
cookies.write(header)
|
250
250
|
|
251
|
-
|
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?(
|
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 <<
|
32
|
+
message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank?
|
33
33
|
message
|
34
34
|
end
|
35
35
|
else
|