grape 0.2.3 → 0.2.4
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.
- data/.yardopts +2 -0
- data/CHANGELOG.markdown +19 -0
- data/Gemfile +2 -0
- data/README.markdown +166 -131
- data/Rakefile +1 -3
- data/lib/grape.rb +0 -3
- data/lib/grape/api.rb +16 -4
- data/lib/grape/cookies.rb +32 -30
- data/lib/grape/endpoint.rb +25 -11
- data/lib/grape/entity.rb +5 -0
- data/lib/grape/error_formatter/base.rb +2 -1
- data/lib/grape/middleware/base.rb +9 -2
- data/lib/grape/middleware/error.rb +11 -11
- data/lib/grape/middleware/formatter.rb +8 -5
- data/lib/grape/middleware/versioner/header.rb +1 -3
- data/lib/grape/middleware/versioner/path.rb +15 -2
- data/lib/grape/util/deep_merge.rb +4 -4
- data/lib/grape/validations.rb +2 -3
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +316 -175
- data/spec/grape/endpoint_spec.rb +159 -57
- data/spec/grape/entity_spec.rb +80 -80
- data/spec/grape/middleware/auth/basic_spec.rb +3 -3
- data/spec/grape/middleware/auth/digest_spec.rb +4 -4
- data/spec/grape/middleware/auth/oauth2_spec.rb +4 -4
- data/spec/grape/middleware/base_spec.rb +9 -9
- data/spec/grape/middleware/error_spec.rb +4 -4
- data/spec/grape/middleware/exception_spec.rb +13 -13
- data/spec/grape/middleware/formatter_spec.rb +25 -25
- data/spec/grape/middleware/versioner/header_spec.rb +23 -23
- data/spec/grape/middleware/versioner/param_spec.rb +8 -8
- data/spec/grape/middleware/versioner/path_spec.rb +8 -8
- data/spec/grape/middleware/versioner_spec.rb +6 -3
- data/spec/grape/util/hash_stack_spec.rb +20 -20
- data/spec/grape/validations/presence_spec.rb +1 -1
- data/spec/grape/validations/regexp_spec.rb +2 -2
- data/spec/grape/validations_spec.rb +4 -4
- data/spec/shared/versioning_examples.rb +48 -20
- metadata +5 -7
- data/.document +0 -5
- data/lib/grape/middleware/prefixer.rb +0 -21
- data/spec/grape/middleware/prefixer_spec.rb +0 -30
data/Rakefile
CHANGED
@@ -19,18 +19,16 @@ task :default => :spec
|
|
19
19
|
|
20
20
|
begin
|
21
21
|
require 'yard'
|
22
|
-
YARD_OPTS = ['-m', 'markdown', '-M', 'maruku']
|
23
22
|
DOC_FILES = ['lib/**/*.rb', 'README.markdown']
|
24
23
|
|
25
24
|
YARD::Rake::YardocTask.new(:doc) do |t|
|
26
25
|
t.files = DOC_FILES
|
27
|
-
t.options = YARD_OPTS
|
28
26
|
end
|
29
27
|
|
30
28
|
namespace :doc do
|
31
29
|
YARD::Rake::YardocTask.new(:pages) do |t|
|
32
30
|
t.files = DOC_FILES
|
33
|
-
t.options =
|
31
|
+
t.options = ['-o', '../grape.doc']
|
34
32
|
end
|
35
33
|
|
36
34
|
namespace :pages do
|
data/lib/grape.rb
CHANGED
@@ -4,8 +4,6 @@ require 'rack/builder'
|
|
4
4
|
module Grape
|
5
5
|
autoload :API, 'grape/api'
|
6
6
|
autoload :Endpoint, 'grape/endpoint'
|
7
|
-
autoload :MiddlewareStack, 'grape/middleware_stack'
|
8
|
-
autoload :Client, 'grape/client'
|
9
7
|
autoload :Route, 'grape/route'
|
10
8
|
autoload :Entity, 'grape/entity'
|
11
9
|
autoload :Cookies, 'grape/cookies'
|
@@ -39,7 +37,6 @@ module Grape
|
|
39
37
|
|
40
38
|
module Middleware
|
41
39
|
autoload :Base, 'grape/middleware/base'
|
42
|
-
autoload :Prefixer, 'grape/middleware/prefixer'
|
43
40
|
autoload :Versioner, 'grape/middleware/versioner'
|
44
41
|
autoload :Formatter, 'grape/middleware/formatter'
|
45
42
|
autoload :Error, 'grape/middleware/error'
|
data/lib/grape/api.rb
CHANGED
@@ -125,9 +125,14 @@ module Grape
|
|
125
125
|
end
|
126
126
|
|
127
127
|
# Specify the format for the API's serializers.
|
128
|
-
# May be `:json
|
128
|
+
# May be `:json`, `:xml`, `:txt`, etc.
|
129
129
|
def format(new_format = nil)
|
130
|
-
|
130
|
+
if new_format
|
131
|
+
set(:format, new_format.to_sym)
|
132
|
+
set(:default_error_formatter, Grape::ErrorFormatter::Base.formatter_for(new_format, {}))
|
133
|
+
else
|
134
|
+
settings[:format]
|
135
|
+
end
|
131
136
|
end
|
132
137
|
|
133
138
|
# Specify a custom formatter for a content-type.
|
@@ -135,6 +140,11 @@ module Grape
|
|
135
140
|
settings.imbue(:formatters, content_type.to_sym => new_formatter)
|
136
141
|
end
|
137
142
|
|
143
|
+
# Specify a default error formatter.
|
144
|
+
def default_error_formatter(new_formatter = nil)
|
145
|
+
new_formatter ? set(:default_error_formatter, new_formatter) : settings[:default_error_formatter]
|
146
|
+
end
|
147
|
+
|
138
148
|
def error_formatter(format, new_formatter)
|
139
149
|
settings.imbue(:error_formatters, format.to_sym => new_formatter)
|
140
150
|
end
|
@@ -209,10 +219,11 @@ module Grape
|
|
209
219
|
# When called without a block, all known helpers within this scope
|
210
220
|
# are included.
|
211
221
|
#
|
212
|
-
# @param
|
213
|
-
# @param
|
222
|
+
# @param [Module] new_mod optional module of methods to include
|
223
|
+
# @param [Block] block optional block of methods to include
|
214
224
|
#
|
215
225
|
# @example Define some helpers.
|
226
|
+
#
|
216
227
|
# class ExampleAPI < Grape::API
|
217
228
|
# helpers do
|
218
229
|
# def current_user
|
@@ -220,6 +231,7 @@ module Grape
|
|
220
231
|
# end
|
221
232
|
# end
|
222
233
|
# end
|
234
|
+
#
|
223
235
|
def helpers(new_mod = nil, &block)
|
224
236
|
if block_given? || new_mod
|
225
237
|
mod = settings.peek[:helpers] || Module.new
|
data/lib/grape/cookies.rb
CHANGED
@@ -1,41 +1,43 @@
|
|
1
1
|
module Grape
|
2
2
|
class Cookies
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
def initialize
|
5
|
+
@cookies = {}
|
6
|
+
@send_cookies = {}
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
9
|
+
def read(request)
|
10
|
+
request.cookies.each do |name, value|
|
11
|
+
@cookies[name.to_s] = value
|
13
12
|
end
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
def write(header)
|
16
|
+
@cookies.select { |key, value|
|
17
|
+
@send_cookies[key] == true
|
18
|
+
}.each { |name, value|
|
19
|
+
cookie_value = value.is_a?(Hash) ? value : { :value => value }
|
20
|
+
Rack::Utils.set_cookie_header! header, name, cookie_value
|
21
|
+
}
|
22
|
+
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
def [](name)
|
25
|
+
@cookies[name.to_s]
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
def []=(name, value)
|
29
|
+
@cookies[name.to_s] = value
|
30
|
+
@send_cookies[name.to_s] = true
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def each(&block)
|
34
|
+
@cookies.each(&block)
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
def delete(name, opts = {})
|
38
|
+
options = opts.merge({ :value => 'deleted', :expires => Time.at(0) })
|
39
|
+
self.[]=(name, options)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/lib/grape/endpoint.rb
CHANGED
@@ -6,9 +6,9 @@ module Grape
|
|
6
6
|
# An Endpoint is the proxy scope in which all routing
|
7
7
|
# blocks are executed. In other words, any methods
|
8
8
|
# on the instance level of this class may be called
|
9
|
-
# from inside a `get`, `post`, etc.
|
9
|
+
# from inside a `get`, `post`, etc.
|
10
10
|
class Endpoint
|
11
|
-
attr_accessor :block, :options, :settings
|
11
|
+
attr_accessor :block, :source, :options, :settings
|
12
12
|
attr_reader :env, :request
|
13
13
|
|
14
14
|
class << self
|
@@ -40,6 +40,7 @@ module Grape
|
|
40
40
|
@settings = settings
|
41
41
|
if block_given?
|
42
42
|
method_name = "#{options[:method]} #{settings.gather(:namespace).join( "/")} #{Array(options[:path]).join("/")}"
|
43
|
+
@source = block
|
43
44
|
@block = self.class.generate_api_method(method_name, &block)
|
44
45
|
end
|
45
46
|
@options = options
|
@@ -55,12 +56,12 @@ module Grape
|
|
55
56
|
end
|
56
57
|
|
57
58
|
def routes
|
58
|
-
@routes ||= prepare_routes
|
59
|
+
@routes ||= endpoints ? endpoints.collect(&:routes).flatten : prepare_routes
|
59
60
|
end
|
60
61
|
|
61
62
|
def mount_in(route_set)
|
62
|
-
if
|
63
|
-
|
63
|
+
if endpoints
|
64
|
+
endpoints.each{|e| e.mount_in(route_set)}
|
64
65
|
else
|
65
66
|
routes.each do |route|
|
66
67
|
route_set.add_route(self, {
|
@@ -109,7 +110,7 @@ module Grape
|
|
109
110
|
|
110
111
|
def prepare_path(path)
|
111
112
|
parts = []
|
112
|
-
parts << settings[:root_prefix] if settings[:root_prefix]
|
113
|
+
parts << settings[:root_prefix].to_s.split("/") if settings[:root_prefix]
|
113
114
|
|
114
115
|
uses_path_versioning = settings[:version] && settings[:version_options][:using] == :path
|
115
116
|
namespace_is_empty = namespace && (namespace.to_s =~ /^\s*$/ || namespace.to_s == '/')
|
@@ -118,11 +119,12 @@ module Grape
|
|
118
119
|
parts << ':version' if uses_path_versioning
|
119
120
|
if !uses_path_versioning || (!namespace_is_empty || !path_is_empty)
|
120
121
|
parts << namespace.to_s if namespace
|
121
|
-
parts << path.to_s if path
|
122
|
+
parts << path.to_s if path
|
122
123
|
format_suffix = '(.:format)'
|
123
124
|
else
|
124
125
|
format_suffix = '(/.:format)'
|
125
126
|
end
|
127
|
+
parts = parts.flatten.select { |part| part != '/' }
|
126
128
|
Rack::Mount::Utils.normalize_path(parts.join('/') + format_suffix)
|
127
129
|
end
|
128
130
|
|
@@ -186,9 +188,10 @@ module Grape
|
|
186
188
|
# Pull out request body params if the content type matches and we're on a POST or PUT
|
187
189
|
def body_params
|
188
190
|
if ['POST', 'PUT'].include?(request.request_method.to_s.upcase) && request.content_length.to_i > 0
|
189
|
-
return
|
191
|
+
return @body_params ||=
|
192
|
+
case env['CONTENT_TYPE']
|
190
193
|
when 'application/json'
|
191
|
-
MultiJson.
|
194
|
+
MultiJson.load(request.body.read)
|
192
195
|
when 'application/xml'
|
193
196
|
MultiXml.parse(request.body.read)
|
194
197
|
else
|
@@ -345,6 +348,16 @@ module Grape
|
|
345
348
|
|
346
349
|
protected
|
347
350
|
|
351
|
+
# Return the collection of endpoints within this endpoint.
|
352
|
+
# This is the case when an Grape::API mounts another Grape::API.
|
353
|
+
def endpoints
|
354
|
+
if options[:app] && options[:app].respond_to?(:endpoints)
|
355
|
+
options[:app].endpoints
|
356
|
+
else
|
357
|
+
nil
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
348
361
|
def run(env)
|
349
362
|
@env = env
|
350
363
|
@header = {}
|
@@ -377,18 +390,19 @@ module Grape
|
|
377
390
|
:default_status => settings[:default_error_status] || 403,
|
378
391
|
:rescue_all => settings[:rescue_all],
|
379
392
|
:rescued_errors => aggregate_setting(:rescued_errors),
|
393
|
+
:default_error_formatter => settings[:default_error_formatter],
|
380
394
|
:error_formatters => settings[:error_formatters],
|
381
395
|
:rescue_options => settings[:rescue_options],
|
382
396
|
:rescue_handlers => merged_setting(:rescue_handlers)
|
383
397
|
|
384
398
|
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
|
385
399
|
b.use Rack::Auth::Digest::MD5, settings[:auth][:realm], settings[:auth][:opaque], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_digest
|
386
|
-
b.use Grape::Middleware::Prefixer, :prefix => settings[:root_prefix] if settings[:root_prefix]
|
387
400
|
|
388
401
|
if settings[:version]
|
389
402
|
b.use Grape::Middleware::Versioner.using(settings[:version_options][:using]), {
|
390
403
|
:versions => settings[:version],
|
391
|
-
:version_options => settings[:version_options]
|
404
|
+
:version_options => settings[:version_options],
|
405
|
+
:prefix => settings[:root_prefix]
|
392
406
|
}
|
393
407
|
end
|
394
408
|
|
data/lib/grape/entity.rb
CHANGED
@@ -330,6 +330,11 @@ module Grape
|
|
330
330
|
|
331
331
|
alias :as_json :serializable_hash
|
332
332
|
|
333
|
+
def to_json(options = {})
|
334
|
+
options = options.to_h if options && options.respond_to?(:to_h)
|
335
|
+
MultiJson.dump(serializable_hash(options))
|
336
|
+
end
|
337
|
+
|
333
338
|
protected
|
334
339
|
|
335
340
|
def key_for(attribute)
|
@@ -5,6 +5,7 @@ module Grape
|
|
5
5
|
class << self
|
6
6
|
|
7
7
|
FORMATTERS = {
|
8
|
+
:serializable_hash => Grape::ErrorFormatter::Json,
|
8
9
|
:json => Grape::ErrorFormatter::Json,
|
9
10
|
:txt => Grape::ErrorFormatter::Txt,
|
10
11
|
:xml => Grape::ErrorFormatter::Xml
|
@@ -18,7 +19,7 @@ module Grape
|
|
18
19
|
spec = formatters(options)[api_format]
|
19
20
|
case spec
|
20
21
|
when nil
|
21
|
-
|
22
|
+
options[:default_error_formatter] || Grape::ErrorFormatter::Txt
|
22
23
|
when Symbol
|
23
24
|
method(spec)
|
24
25
|
else
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/ordered_hash'
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
3
|
require 'multi_json'
|
3
4
|
require 'multi_xml'
|
4
5
|
|
@@ -24,7 +25,9 @@ module Grape
|
|
24
25
|
@options = default_options.merge(options)
|
25
26
|
end
|
26
27
|
|
27
|
-
def default_options
|
28
|
+
def default_options
|
29
|
+
{}
|
30
|
+
end
|
28
31
|
|
29
32
|
def call(env)
|
30
33
|
dup.call!(env)
|
@@ -53,12 +56,16 @@ module Grape
|
|
53
56
|
Rack::Response.new(@app_response)
|
54
57
|
end
|
55
58
|
|
59
|
+
def content_type_for(format)
|
60
|
+
HashWithIndifferentAccess.new(content_types)[format]
|
61
|
+
end
|
62
|
+
|
56
63
|
def content_types
|
57
64
|
options[:content_types] || CONTENT_TYPES
|
58
65
|
end
|
59
66
|
|
60
67
|
def content_type
|
61
|
-
|
68
|
+
content_type_for(env['api.format'] || options[:format]) || 'text/html'
|
62
69
|
end
|
63
70
|
|
64
71
|
def mime_types
|
@@ -6,17 +6,17 @@ module Grape
|
|
6
6
|
class Error < Base
|
7
7
|
|
8
8
|
def default_options
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
{
|
10
|
+
:default_status => 403, # default status returned on error
|
11
|
+
:default_message => "",
|
12
|
+
:format => :txt,
|
13
|
+
:formatters => {},
|
14
|
+
:error_formatters => {},
|
15
|
+
:rescue_all => false, # true to rescue all exceptions
|
16
|
+
:rescue_options => { :backtrace => false }, # true to display backtrace
|
17
|
+
:rescue_handlers => {}, # rescue handler blocks
|
18
|
+
:rescued_errors => []
|
19
|
+
}
|
20
20
|
end
|
21
21
|
|
22
22
|
def call!(env)
|
@@ -18,7 +18,7 @@ module Grape
|
|
18
18
|
|
19
19
|
def before
|
20
20
|
fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
|
21
|
-
if
|
21
|
+
if content_type_for(fmt)
|
22
22
|
if !env['rack.input'].nil? and (body = env['rack.input'].read).strip.length != 0
|
23
23
|
parser = Grape::Parser::Base.parser_for fmt, options
|
24
24
|
unless parser.nil?
|
@@ -42,15 +42,18 @@ module Grape
|
|
42
42
|
parts = request.path.split('.')
|
43
43
|
|
44
44
|
if parts.size > 1
|
45
|
-
extension = parts.last
|
46
|
-
|
45
|
+
extension = parts.last
|
46
|
+
# avoid symbol memory leak on an unknown format
|
47
|
+
return extension.to_sym if content_type_for(extension)
|
47
48
|
end
|
48
49
|
nil
|
49
50
|
end
|
50
51
|
|
51
52
|
def format_from_params
|
52
53
|
fmt = Rack::Utils.parse_nested_query(env['QUERY_STRING'])["format"]
|
53
|
-
|
54
|
+
# avoid symbol memory leak on an unknown format
|
55
|
+
return fmt.to_sym if content_type_for(fmt)
|
56
|
+
fmt
|
54
57
|
end
|
55
58
|
|
56
59
|
def format_from_header
|
@@ -76,7 +79,7 @@ module Grape
|
|
76
79
|
bodymap = bodies.collect do |body|
|
77
80
|
formatter.call body, env
|
78
81
|
end
|
79
|
-
headers['Content-Type'] =
|
82
|
+
headers['Content-Type'] = content_type_for(env['api.format']) unless headers['Content-Type']
|
80
83
|
Rack::Response.new(bodymap, status, headers).to_a
|
81
84
|
end
|
82
85
|
|
@@ -19,11 +19,9 @@ module Grape
|
|
19
19
|
# env['api.version] => 'v1'
|
20
20
|
# env['api.format] => 'format'
|
21
21
|
#
|
22
|
-
# If version does not match this route, then a 406 is
|
22
|
+
# If version does not match this route, then a 406 is raised with
|
23
23
|
# X-Cascade header to alert Rack::Mount to attempt the next matched
|
24
24
|
# route.
|
25
|
-
#
|
26
|
-
# @throws [RuntimeError] if Accept header is invalid
|
27
25
|
class Header < Base
|
28
26
|
|
29
27
|
def before
|
@@ -24,7 +24,14 @@ module Grape
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def before
|
27
|
-
|
27
|
+
path = env['PATH_INFO'].dup
|
28
|
+
|
29
|
+
if prefix && path.index(prefix) == 0
|
30
|
+
path.sub!(prefix, '')
|
31
|
+
path = Rack::Mount::Utils.normalize_path(path)
|
32
|
+
end
|
33
|
+
|
34
|
+
pieces = path.split('/')
|
28
35
|
potential_version = pieces[1]
|
29
36
|
if potential_version =~ options[:pattern]
|
30
37
|
if options[:versions] && !options[:versions].include?(potential_version)
|
@@ -33,9 +40,15 @@ module Grape
|
|
33
40
|
|
34
41
|
truncated_path = "/#{pieces[2..-1].join('/')}"
|
35
42
|
env['api.version'] = potential_version
|
36
|
-
env['PATH_INFO'] = truncated_path
|
37
43
|
end
|
38
44
|
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def prefix
|
49
|
+
Rack::Mount::Utils.normalize_path(options[:prefix].to_s) if options[:prefix]
|
50
|
+
end
|
51
|
+
|
39
52
|
end
|
40
53
|
end
|
41
54
|
end
|