grape 0.1.5 → 0.2.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.
- data/.gitignore +13 -1
- data/.rspec +1 -1
- data/.travis.yml +1 -0
- data/Gemfile +10 -0
- data/Guardfile +15 -0
- data/README.markdown +472 -67
- data/grape.gemspec +4 -4
- data/lib/grape.rb +17 -5
- data/lib/grape/api.rb +227 -124
- data/lib/grape/cookies.rb +41 -0
- data/lib/grape/endpoint.rb +256 -27
- data/lib/grape/entity.rb +227 -0
- data/lib/grape/middleware/auth/oauth2.rb +1 -2
- data/lib/grape/middleware/base.rb +59 -6
- data/lib/grape/middleware/error.rb +15 -6
- data/lib/grape/middleware/filter.rb +17 -0
- data/lib/grape/middleware/formatter.rb +25 -31
- data/lib/grape/middleware/versioner.rb +20 -20
- data/lib/grape/middleware/versioner/header.rb +59 -0
- data/lib/grape/middleware/versioner/path.rb +42 -0
- data/lib/grape/route.rb +23 -0
- data/lib/grape/util/hash_stack.rb +100 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +651 -162
- data/spec/grape/endpoint_spec.rb +216 -18
- data/spec/grape/entity_spec.rb +320 -0
- data/spec/grape/middleware/auth/basic_spec.rb +2 -2
- data/spec/grape/middleware/auth/digest_spec.rb +4 -6
- data/spec/grape/middleware/exception_spec.rb +1 -0
- data/spec/grape/middleware/formatter_spec.rb +81 -27
- data/spec/grape/middleware/versioner/header_spec.rb +148 -0
- data/spec/grape/middleware/versioner/path_spec.rb +40 -0
- data/spec/grape/middleware/versioner_spec.rb +6 -34
- data/spec/grape/util/hash_stack_spec.rb +133 -0
- data/spec/shared/versioning_examples.rb +77 -0
- data/spec/spec_helper.rb +11 -3
- data/spec/support/basic_auth_encode_helpers.rb +4 -0
- data/spec/support/rack_patch.rb +25 -0
- data/spec/support/versioned_helpers.rb +34 -0
- metadata +140 -241
- data/.yardoc/checksums +0 -13
- data/.yardoc/objects/Grape.dat +0 -0
- data/.yardoc/objects/Grape/API.dat +0 -0
- data/.yardoc/objects/Grape/API/auth_c.dat +0 -0
- data/.yardoc/objects/Grape/API/build_endpoint_c.dat +0 -0
- data/.yardoc/objects/Grape/API/call_c.dat +0 -0
- data/.yardoc/objects/Grape/API/compile_path_c.dat +0 -0
- data/.yardoc/objects/Grape/API/default_format_c.dat +0 -0
- data/.yardoc/objects/Grape/API/delete_c.dat +0 -0
- data/.yardoc/objects/Grape/API/get_c.dat +0 -0
- data/.yardoc/objects/Grape/API/group_c.dat +0 -0
- data/.yardoc/objects/Grape/API/head_c.dat +0 -0
- data/.yardoc/objects/Grape/API/helpers_c.dat +0 -0
- data/.yardoc/objects/Grape/API/http_basic_c.dat +0 -0
- data/.yardoc/objects/Grape/API/inherited_c.dat +0 -0
- data/.yardoc/objects/Grape/API/logger_c.dat +0 -0
- data/.yardoc/objects/Grape/API/namespace_c.dat +0 -0
- data/.yardoc/objects/Grape/API/nest_c.dat +0 -0
- data/.yardoc/objects/Grape/API/post_c.dat +0 -0
- data/.yardoc/objects/Grape/API/prefix_c.dat +0 -0
- data/.yardoc/objects/Grape/API/put_c.dat +0 -0
- data/.yardoc/objects/Grape/API/reset_21_c.dat +0 -0
- data/.yardoc/objects/Grape/API/resource_c.dat +0 -0
- data/.yardoc/objects/Grape/API/resources_c.dat +0 -0
- data/.yardoc/objects/Grape/API/route_c.dat +0 -0
- data/.yardoc/objects/Grape/API/route_set_c.dat +0 -0
- data/.yardoc/objects/Grape/API/scope_c.dat +0 -0
- data/.yardoc/objects/Grape/API/set_c.dat +0 -0
- data/.yardoc/objects/Grape/API/settings_c.dat +0 -0
- data/.yardoc/objects/Grape/API/settings_stack_c.dat +0 -0
- data/.yardoc/objects/Grape/API/version_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/block_3D_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/block_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/call_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/call_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/env_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/error_21_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/generate_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/header_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/params_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/request_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/status_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/version_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/authenticator_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/basic_request_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/credentials_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/initialize_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/error_out_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/parse_authorization_header_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/token_class_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/verify_token_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/after_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/app_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/call_21_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/call_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/env_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/initialize_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/request_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/response_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Error.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Error/call_21_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Error/error_response_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/CONTENT_TYPES.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/after_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/content_types_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/encode_json_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/encode_txt_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/format_from_extension_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/format_from_header_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/headers_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/mime_array_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/mime_types_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Prefixer.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Prefixer/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Prefixer/prefix_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Versioner.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Versioner/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Versioner/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/initialize_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/stack_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/to_app_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/use_i.dat +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -2
- data/Gemfile.lock +0 -52
- data/autotest/discover.rb +0 -1
@@ -60,13 +60,12 @@ module Grape::Middleware::Auth
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def error_out(status, error)
|
63
|
-
throw :error,
|
63
|
+
throw :error,
|
64
64
|
:message => error,
|
65
65
|
:status => status,
|
66
66
|
:headers => {
|
67
67
|
'WWW-Authenticate' => "OAuth realm='#{options[:realm]}', error='#{error}'"
|
68
68
|
}
|
69
|
-
}
|
70
69
|
end
|
71
70
|
end
|
72
71
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'multi_xml'
|
3
|
+
|
1
4
|
module Grape
|
2
5
|
module Middleware
|
3
6
|
class Base
|
@@ -9,20 +12,20 @@ module Grape
|
|
9
12
|
@app = app
|
10
13
|
@options = default_options.merge(options)
|
11
14
|
end
|
12
|
-
|
15
|
+
|
13
16
|
def default_options; {} end
|
14
|
-
|
17
|
+
|
15
18
|
def call(env)
|
16
19
|
dup.call!(env)
|
17
20
|
end
|
18
|
-
|
21
|
+
|
19
22
|
def call!(env)
|
20
23
|
@env = env
|
21
24
|
before
|
22
25
|
@app_response = @app.call(@env)
|
23
26
|
after || @app_response
|
24
27
|
end
|
25
|
-
|
28
|
+
|
26
29
|
# @abstract
|
27
30
|
# Called before the application is called in the middleware lifecycle.
|
28
31
|
def before; end
|
@@ -30,11 +33,11 @@ module Grape
|
|
30
33
|
# Called after the application is called in the middleware lifecycle.
|
31
34
|
# @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
|
32
35
|
def after; end
|
33
|
-
|
36
|
+
|
34
37
|
def request
|
35
38
|
Rack::Request.new(self.env)
|
36
39
|
end
|
37
|
-
|
40
|
+
|
38
41
|
def response
|
39
42
|
Rack::Response.new(@app_response)
|
40
43
|
end
|
@@ -52,12 +55,21 @@ module Grape
|
|
52
55
|
FORMATTERS = {
|
53
56
|
:json => :encode_json,
|
54
57
|
:txt => :encode_txt,
|
58
|
+
:xml => :encode_xml
|
59
|
+
}
|
60
|
+
PARSERS = {
|
61
|
+
:json => :decode_json,
|
62
|
+
:xml => :decode_xml
|
55
63
|
}
|
56
64
|
|
57
65
|
def formatters
|
58
66
|
FORMATTERS.merge(options[:formatters] || {})
|
59
67
|
end
|
60
68
|
|
69
|
+
def parsers
|
70
|
+
PARSERS.merge(options[:parsers] || {})
|
71
|
+
end
|
72
|
+
|
61
73
|
def content_types
|
62
74
|
CONTENT_TYPES.merge(options[:content_types] || {})
|
63
75
|
end
|
@@ -82,6 +94,47 @@ module Grape
|
|
82
94
|
end
|
83
95
|
end
|
84
96
|
|
97
|
+
def parser_for(api_format)
|
98
|
+
spec = parsers[api_format]
|
99
|
+
case spec
|
100
|
+
when nil
|
101
|
+
nil
|
102
|
+
when Symbol
|
103
|
+
method(spec)
|
104
|
+
else
|
105
|
+
spec
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def decode_json(object)
|
110
|
+
MultiJson.decode(object)
|
111
|
+
end
|
112
|
+
|
113
|
+
def encode_json(object)
|
114
|
+
return object if object.is_a?(String)
|
115
|
+
|
116
|
+
if object.respond_to? :serializable_hash
|
117
|
+
MultiJson.encode(object.serializable_hash)
|
118
|
+
elsif object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false)
|
119
|
+
MultiJson.encode(object.map {|o| o.serializable_hash })
|
120
|
+
elsif object.respond_to? :to_json
|
121
|
+
object.to_json
|
122
|
+
else
|
123
|
+
MultiJson.encode(object)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def encode_txt(object)
|
128
|
+
object.respond_to?(:to_txt) ? object.to_txt : object.to_s
|
129
|
+
end
|
130
|
+
|
131
|
+
def decode_xml(object)
|
132
|
+
MultiXml.parse(object)
|
133
|
+
end
|
134
|
+
|
135
|
+
def encode_xml(object)
|
136
|
+
object.respond_to?(:to_xml) ? object.to_xml : object.to_s
|
137
|
+
end
|
85
138
|
end
|
86
139
|
|
87
140
|
end
|
@@ -12,9 +12,9 @@ module Grape
|
|
12
12
|
:default_message => "",
|
13
13
|
:format => :txt,
|
14
14
|
:formatters => {},
|
15
|
-
|
16
15
|
:rescue_all => false, # true to rescue all exceptions
|
17
|
-
:rescue_options => {:backtrace => false}, # true to display backtrace
|
16
|
+
:rescue_options => { :backtrace => false }, # true to display backtrace
|
17
|
+
:rescue_handlers => {}, # rescue handler blocks
|
18
18
|
:rescued_errors => []
|
19
19
|
}
|
20
20
|
end
|
@@ -45,23 +45,32 @@ module Grape
|
|
45
45
|
})
|
46
46
|
rescue Exception => e
|
47
47
|
raise unless options[:rescue_all] || (options[:rescued_errors] || []).include?(e.class)
|
48
|
-
|
48
|
+
handler = options[:rescue_handlers][e.class] || options[:rescue_handlers][:all]
|
49
|
+
handler.nil? ? handle_error(e) : self.instance_exec(e, &handler)
|
49
50
|
end
|
50
51
|
|
51
52
|
end
|
52
53
|
|
54
|
+
def handle_error(e)
|
55
|
+
error_response({ :message => e.message, :backtrace => e.backtrace })
|
56
|
+
end
|
57
|
+
|
53
58
|
def error_response(error = {})
|
54
59
|
status = error[:status] || options[:default_status]
|
55
60
|
message = error[:message] || options[:default_message]
|
56
61
|
headers = {'Content-Type' => content_type}
|
57
62
|
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
58
63
|
backtrace = error[:backtrace] || []
|
59
|
-
|
64
|
+
rack_response(format_message(message, backtrace, status), status, headers)
|
60
65
|
end
|
61
|
-
|
66
|
+
|
67
|
+
def rack_response(message, status = options[:default_status], headers = { 'Content-Type' => content_type })
|
68
|
+
Rack::Response.new([ message ], status, headers).finish
|
69
|
+
end
|
70
|
+
|
62
71
|
def format_message(message, backtrace, status)
|
63
72
|
formatter = formatter_for(options[:format])
|
64
|
-
throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter
|
73
|
+
throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter
|
65
74
|
formatter.call(message, backtrace)
|
66
75
|
end
|
67
76
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Grape
|
2
|
+
module Middleware
|
3
|
+
# This is a simple middleware for adding before and after filters
|
4
|
+
# to Grape APIs. It is used like so:
|
5
|
+
#
|
6
|
+
# use Grape::Middleware::Filter, :before => lambda{ do_something }, after: => lambda{ do_something }
|
7
|
+
class Filter < Base
|
8
|
+
def before
|
9
|
+
app.instance_eval &options[:before] if options[:before]
|
10
|
+
end
|
11
|
+
|
12
|
+
def after
|
13
|
+
app.instance_eval &options[:after] if options[:after]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'grape/middleware/base'
|
2
|
-
require 'multi_json'
|
3
2
|
|
4
3
|
module Grape
|
5
4
|
module Middleware
|
@@ -7,22 +6,35 @@ module Grape
|
|
7
6
|
include Formats
|
8
7
|
|
9
8
|
def default_options
|
10
|
-
{
|
9
|
+
{
|
11
10
|
:default_format => :txt,
|
12
11
|
:formatters => {},
|
13
|
-
:content_types => {}
|
12
|
+
:content_types => {},
|
13
|
+
:parsers => {}
|
14
14
|
}
|
15
15
|
end
|
16
16
|
|
17
17
|
def headers
|
18
|
-
env.dup.inject({}){|h,(k,v)| h[k.downcase] = v; h}
|
18
|
+
env.dup.inject({}){|h,(k,v)| h[k.downcase[5..-1]] = v if k.downcase.start_with?('http_'); h}
|
19
19
|
end
|
20
20
|
|
21
21
|
def before
|
22
|
-
fmt = format_from_extension || format_from_header || options[:default_format]
|
23
|
-
|
22
|
+
fmt = format_from_extension || options[:format] || format_from_header || options[:default_format]
|
24
23
|
if content_types.key?(fmt)
|
25
|
-
env['
|
24
|
+
if !env['rack.input'].nil? and (body = env['rack.input'].read).strip.length != 0
|
25
|
+
parser = parser_for fmt
|
26
|
+
unless parser.nil?
|
27
|
+
begin
|
28
|
+
body = parser.call(body)
|
29
|
+
env['rack.request.form_hash'] = !env['rack.request.form_hash'].nil? ? env['rack.request.form_hash'].merge(body) : body
|
30
|
+
env['rack.request.form_input'] = env['rack.input']
|
31
|
+
rescue
|
32
|
+
# It's possible that it's just regular POST content -- just back off
|
33
|
+
end
|
34
|
+
end
|
35
|
+
env['rack.input'].rewind
|
36
|
+
end
|
37
|
+
env['api.format'] = fmt
|
26
38
|
else
|
27
39
|
throw :error, :status => 406, :message => 'The requested format is not supported.'
|
28
40
|
end
|
@@ -40,7 +52,7 @@ module Grape
|
|
40
52
|
end
|
41
53
|
|
42
54
|
def format_from_header
|
43
|
-
mime_array.each do |t|
|
55
|
+
mime_array.each do |t|
|
44
56
|
if mime_types.key?(t)
|
45
57
|
return mime_types[t]
|
46
58
|
end
|
@@ -49,15 +61,11 @@ module Grape
|
|
49
61
|
end
|
50
62
|
|
51
63
|
def mime_array
|
52
|
-
accept = headers['accept']
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
map{|a| a[0]}
|
58
|
-
else
|
59
|
-
[]
|
60
|
-
end
|
64
|
+
accept = headers['accept'] or return []
|
65
|
+
|
66
|
+
accept.gsub(/\b/,'').scan(%r((\w+/[\w+.-]+)(?:(?:;[^,]*?)?;\s*q=([\d.]+))?)).sort_by { |_, q| -q.to_f }.map {|mime, _|
|
67
|
+
mime.sub(%r(vnd\.[^+]+\+), '')
|
68
|
+
}
|
61
69
|
end
|
62
70
|
|
63
71
|
def after
|
@@ -69,20 +77,6 @@ module Grape
|
|
69
77
|
headers['Content-Type'] = content_types[env['api.format']]
|
70
78
|
Rack::Response.new(bodymap, status, headers).to_a
|
71
79
|
end
|
72
|
-
|
73
|
-
def encode_json(object)
|
74
|
-
if object.respond_to? :serializable_hash
|
75
|
-
MultiJson.encode(object.serializable_hash)
|
76
|
-
elsif object.respond_to? :to_json
|
77
|
-
object.to_json
|
78
|
-
else
|
79
|
-
MultiJson.encode(object)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def encode_txt(object)
|
84
|
-
object.respond_to?(:to_txt) ? object.to_txt : object.to_s
|
85
|
-
end
|
86
80
|
end
|
87
81
|
end
|
88
82
|
end
|
@@ -1,25 +1,25 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# Versioners set env['api.version'] when a version is defined on an API and
|
2
|
+
# on the requests. The current methods for determining version are:
|
3
|
+
#
|
4
|
+
# :header - version from HTTP Accept header.
|
5
|
+
# :path - version from uri. e.g. /v1/resource
|
6
|
+
#
|
7
|
+
# See individual classes for details.
|
3
8
|
module Grape
|
4
9
|
module Middleware
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
truncated_path = "/#{pieces[2..-1].join('/')}"
|
21
|
-
env['api.version'] = potential_version
|
22
|
-
env['PATH_INFO'] = truncated_path
|
10
|
+
module Versioner
|
11
|
+
extend self
|
12
|
+
|
13
|
+
# @param strategy [Symbol] :path or :header
|
14
|
+
# @return a middleware class based on strategy
|
15
|
+
def using(strategy)
|
16
|
+
case strategy
|
17
|
+
when :path
|
18
|
+
Path
|
19
|
+
when :header
|
20
|
+
Header
|
21
|
+
else
|
22
|
+
raise ArgumentError.new("Unknown :using for versioner: #{strategy}")
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Versioner
|
6
|
+
# This middleware sets various version related rack environment variables
|
7
|
+
# based on the HTTP Accept header with the pattern:
|
8
|
+
# application/vnd.:vendor-:version+:format
|
9
|
+
#
|
10
|
+
# Example: For request header
|
11
|
+
# Accept: application/vnd.mycompany-v1+json
|
12
|
+
#
|
13
|
+
# The following rack env variables are set:
|
14
|
+
#
|
15
|
+
# env['api.type'] => 'application'
|
16
|
+
# env['api.subtype'] => 'vnd.mycompany-v1+json'
|
17
|
+
# env['api.vendor] => 'mycompany'
|
18
|
+
# env['api.version] => 'v1'
|
19
|
+
# env['api.format] => 'format'
|
20
|
+
#
|
21
|
+
# If version does not match this route, then a 406 is throw with
|
22
|
+
# X-Cascade header to alert Rack::Mount to attempt the next matched
|
23
|
+
# route.
|
24
|
+
class Header < Base
|
25
|
+
def before
|
26
|
+
accept = env['HTTP_ACCEPT'] || ""
|
27
|
+
|
28
|
+
if options[:version_options] && options[:version_options].keys.include?(:strict) && options[:version_options][:strict]
|
29
|
+
if (is_accept_header_valid?(accept)) && options[:version_options][:using] == :header
|
30
|
+
throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
accept.strip.scan(/^(.+?)\/(.+?)$/) do |type, subtype|
|
34
|
+
env['api.type'] = type
|
35
|
+
env['api.subtype'] = subtype
|
36
|
+
|
37
|
+
subtype.scan(/vnd\.(.+)?-(.+)?\+(.*)?/) do |vendor, version, format|
|
38
|
+
is_vendored = options[:version_options] && options[:version_options][:vendor]
|
39
|
+
is_vendored_match = is_vendored ? options[:version_options][:vendor] == vendor : true
|
40
|
+
|
41
|
+
if (options[:versions] && !options[:versions].include?(version)) || !is_vendored_match
|
42
|
+
throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found"
|
43
|
+
end
|
44
|
+
|
45
|
+
env['api.version'] = version
|
46
|
+
env['api.vendor'] = vendor
|
47
|
+
env['api.format'] = format # weird that Grape::Middleware::Formatter also does this
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
def is_accept_header_valid?(header)
|
54
|
+
(header.strip =~ /application\/vnd\.(.+?)-(.+?)\+(.+?)/).nil?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Versioner
|
6
|
+
# This middleware sets various version related rack environment variables
|
7
|
+
# based on the uri path and removes the version substring from the uri
|
8
|
+
# path. If the version substring does not match any potential initialized
|
9
|
+
# versions, a 404 error is thrown.
|
10
|
+
#
|
11
|
+
# Example: For a uri path
|
12
|
+
# /v1/resource
|
13
|
+
#
|
14
|
+
# The following rack env variables are set and path is rewritten to
|
15
|
+
# '/resource':
|
16
|
+
#
|
17
|
+
# env['api.version'] => 'v1'
|
18
|
+
#
|
19
|
+
class Path < Base
|
20
|
+
def default_options
|
21
|
+
{
|
22
|
+
:pattern => /.*/i
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def before
|
27
|
+
pieces = env['PATH_INFO'].split('/')
|
28
|
+
potential_version = pieces[1]
|
29
|
+
if potential_version =~ options[:pattern]
|
30
|
+
if options[:versions] && !options[:versions].include?(potential_version)
|
31
|
+
throw :error, :status => 404, :message => "404 API Version Not Found"
|
32
|
+
end
|
33
|
+
|
34
|
+
truncated_path = "/#{pieces[2..-1].join('/')}"
|
35
|
+
env['api.version'] = potential_version
|
36
|
+
env['PATH_INFO'] = truncated_path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/grape/route.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Grape
|
2
|
+
|
3
|
+
# A compiled route for inspection.
|
4
|
+
class Route
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@options = options || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(method_id, *arguments)
|
11
|
+
if match = /route_([_a-zA-Z]\w*)/.match(method_id.to_s)
|
12
|
+
@options[match.captures.last.to_sym]
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"version=#{route_version}, method=#{route_method}, path=#{route_path}"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|