grape 0.6.0 → 0.6.1
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 +8 -8
- data/.rubocop.yml +65 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +17 -1
- data/Gemfile +1 -0
- data/README.md +16 -8
- data/RELEASING.md +105 -0
- data/grape.gemspec +1 -1
- data/lib/grape.rb +1 -0
- data/lib/grape/api.rb +88 -54
- data/lib/grape/cookies.rb +4 -6
- data/lib/grape/endpoint.rb +81 -69
- data/lib/grape/error_formatter/base.rb +5 -4
- data/lib/grape/error_formatter/json.rb +3 -3
- data/lib/grape/error_formatter/txt.rb +1 -1
- data/lib/grape/error_formatter/xml.rb +4 -4
- data/lib/grape/exceptions/base.rb +7 -7
- data/lib/grape/exceptions/incompatible_option_values.rb +13 -0
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -4
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/missing_option.rb +1 -1
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
- data/lib/grape/exceptions/unknown_options.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/validation.rb +2 -2
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- data/lib/grape/formatter/base.rb +5 -4
- data/lib/grape/formatter/serializable_hash.rb +7 -6
- data/lib/grape/http/request.rb +2 -2
- data/lib/grape/locale/en.yml +2 -0
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/auth/basic.rb +1 -1
- data/lib/grape/middleware/auth/oauth2.rb +18 -20
- data/lib/grape/middleware/base.rb +1 -1
- data/lib/grape/middleware/error.rb +19 -19
- data/lib/grape/middleware/filter.rb +3 -3
- data/lib/grape/middleware/formatter.rb +29 -23
- data/lib/grape/middleware/versioner.rb +1 -1
- data/lib/grape/middleware/versioner/accept_version_header.rb +8 -6
- data/lib/grape/middleware/versioner/header.rb +16 -14
- data/lib/grape/middleware/versioner/param.rb +7 -7
- data/lib/grape/middleware/versioner/path.rb +7 -9
- data/lib/grape/parser/base.rb +3 -2
- data/lib/grape/path.rb +1 -1
- data/lib/grape/route.rb +6 -4
- data/lib/grape/util/content_types.rb +2 -1
- data/lib/grape/util/deep_merge.rb +5 -5
- data/lib/grape/util/hash_stack.rb +2 -2
- data/lib/grape/validations.rb +34 -30
- data/lib/grape/validations/coerce.rb +6 -5
- data/lib/grape/validations/default.rb +0 -1
- data/lib/grape/validations/presence.rb +1 -1
- data/lib/grape/validations/regexp.rb +2 -2
- data/lib/grape/validations/values.rb +16 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +229 -210
- data/spec/grape/endpoint_spec.rb +56 -54
- data/spec/grape/entity_spec.rb +31 -33
- data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
- data/spec/grape/middleware/auth/basic_spec.rb +8 -8
- data/spec/grape/middleware/auth/digest_spec.rb +5 -5
- data/spec/grape/middleware/auth/oauth2_spec.rb +23 -23
- data/spec/grape/middleware/base_spec.rb +6 -6
- data/spec/grape/middleware/error_spec.rb +11 -15
- data/spec/grape/middleware/exception_spec.rb +45 -25
- data/spec/grape/middleware/formatter_spec.rb +56 -45
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +25 -25
- data/spec/grape/middleware/versioner/header_spec.rb +54 -54
- data/spec/grape/middleware/versioner/param_spec.rb +17 -18
- data/spec/grape/middleware/versioner/path_spec.rb +6 -6
- data/spec/grape/middleware/versioner_spec.rb +1 -1
- data/spec/grape/util/hash_stack_spec.rb +26 -27
- data/spec/grape/validations/coerce_spec.rb +39 -34
- data/spec/grape/validations/default_spec.rb +12 -13
- data/spec/grape/validations/presence_spec.rb +18 -22
- data/spec/grape/validations/regexp_spec.rb +9 -9
- data/spec/grape/validations/values_spec.rb +64 -0
- data/spec/grape/validations_spec.rb +127 -70
- data/spec/shared/versioning_examples.rb +5 -5
- data/spec/support/basic_auth_encode_helpers.rb +0 -1
- data/spec/support/versioned_helpers.rb +5 -6
- metadata +10 -4
@@ -4,12 +4,9 @@ module Grape
|
|
4
4
|
class InvalidWithOptionForRepresent < Base
|
5
5
|
|
6
6
|
def initialize
|
7
|
-
super(:
|
7
|
+
super(message: compose_message("invalid_with_option_for_represent"))
|
8
8
|
end
|
9
9
|
|
10
10
|
end
|
11
|
-
|
12
11
|
end
|
13
|
-
|
14
12
|
end
|
15
|
-
|
@@ -4,7 +4,7 @@ module Grape
|
|
4
4
|
class UnknownValidator < Base
|
5
5
|
|
6
6
|
def initialize(validator_type)
|
7
|
-
super(:
|
7
|
+
super(message: compose_message("unknown_validator", validator_type: validator_type))
|
8
8
|
end
|
9
9
|
|
10
10
|
end
|
data/lib/grape/formatter/base.rb
CHANGED
@@ -5,10 +5,11 @@ module Grape
|
|
5
5
|
class << self
|
6
6
|
|
7
7
|
FORMATTERS = {
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
8
|
+
json: Grape::Formatter::Json,
|
9
|
+
jsonapi: Grape::Formatter::Json,
|
10
|
+
serializable_hash: Grape::Formatter::SerializableHash,
|
11
|
+
txt: Grape::Formatter::Txt,
|
12
|
+
xml: Grape::Formatter::Xml
|
12
13
|
}
|
13
14
|
|
14
15
|
def formatters(options)
|
@@ -13,18 +13,19 @@ module Grape
|
|
13
13
|
private
|
14
14
|
|
15
15
|
def serializable?(object)
|
16
|
-
|
17
|
-
object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false) ||
|
18
|
-
object.kind_of?(Hash)
|
16
|
+
object.respond_to?(:serializable_hash) || object.kind_of?(Array) && !object.map { |o| o.respond_to? :serializable_hash }.include?(false) || object.kind_of?(Hash)
|
19
17
|
end
|
20
18
|
|
21
19
|
def serialize(object)
|
22
20
|
if object.respond_to? :serializable_hash
|
23
21
|
object.serializable_hash
|
24
|
-
elsif object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false)
|
25
|
-
object.map {|o| o.serializable_hash }
|
22
|
+
elsif object.kind_of?(Array) && !object.map { |o| o.respond_to? :serializable_hash }.include?(false)
|
23
|
+
object.map { |o| o.serializable_hash }
|
26
24
|
elsif object.kind_of?(Hash)
|
27
|
-
object.inject({})
|
25
|
+
object.inject({}) do |h, (k, v)|
|
26
|
+
h[k] = serialize(v)
|
27
|
+
h
|
28
|
+
end
|
28
29
|
else
|
29
30
|
object
|
30
31
|
end
|
data/lib/grape/http/request.rb
CHANGED
@@ -15,13 +15,13 @@ module Grape
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def headers
|
18
|
-
@env['grape.request.headers'] ||= @env.dup.inject({})
|
18
|
+
@env['grape.request.headers'] ||= @env.dup.inject({}) do |h, (k, v)|
|
19
19
|
if k.to_s.start_with? 'HTTP_'
|
20
20
|
k = k[5..-1].gsub('_', '-').downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }
|
21
21
|
h[k] = v
|
22
22
|
end
|
23
23
|
h
|
24
|
-
|
24
|
+
end
|
25
25
|
end
|
26
26
|
|
27
27
|
end
|
data/lib/grape/locale/en.yml
CHANGED
@@ -6,6 +6,7 @@ en:
|
|
6
6
|
coerce: 'is invalid'
|
7
7
|
presence: 'is missing'
|
8
8
|
regexp: 'is invalid'
|
9
|
+
values: 'does not have a valid value'
|
9
10
|
missing_vendor_option:
|
10
11
|
problem: 'missing :vendor option.'
|
11
12
|
summary: 'when version using header, you must specify :vendor option. '
|
@@ -26,3 +27,4 @@ en:
|
|
26
27
|
resolution: 'available strategy for :using is :path, :header, :param'
|
27
28
|
unknown_validator: 'unknown validator: %{validator_type}'
|
28
29
|
unknown_options: 'unknown options: %{options}'
|
30
|
+
incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
|
@@ -12,16 +12,16 @@ module Grape
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def base_request
|
15
|
-
raise NotImplementedError
|
15
|
+
raise NotImplementedError, "You must implement base_request."
|
16
16
|
end
|
17
17
|
|
18
18
|
def credentials
|
19
|
-
base_request.provided
|
19
|
+
base_request.provided? ? base_request.credentials : [nil, nil]
|
20
20
|
end
|
21
21
|
|
22
22
|
def before
|
23
23
|
unless authenticator.call(*credentials)
|
24
|
-
throw :error, :
|
24
|
+
throw :error, status: 401, message: "API Authorization Failed."
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -3,14 +3,14 @@ module Grape::Middleware::Auth
|
|
3
3
|
class OAuth2 < Grape::Middleware::Base
|
4
4
|
def default_options
|
5
5
|
{
|
6
|
-
:
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
6
|
+
token_class: 'AccessToken',
|
7
|
+
realm: 'OAuth API',
|
8
|
+
parameter: %w(bearer_token oauth_token),
|
9
|
+
accepted_headers: %w(HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION),
|
10
|
+
header: [/Bearer (.*)/i, /OAuth (.*)/i]
|
11
11
|
}
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def before
|
15
15
|
verify_token(token_parameter || token_header)
|
16
16
|
end
|
@@ -25,9 +25,7 @@ module Grape::Middleware::Auth
|
|
25
25
|
def token_header
|
26
26
|
return false unless authorization_header
|
27
27
|
Array(options[:header]).each do |regexp|
|
28
|
-
if authorization_header =~ regexp
|
29
|
-
return $1
|
30
|
-
end
|
28
|
+
return $1 if authorization_header =~ regexp
|
31
29
|
end
|
32
30
|
nil
|
33
31
|
end
|
@@ -38,13 +36,14 @@ module Grape::Middleware::Auth
|
|
38
36
|
end
|
39
37
|
nil
|
40
38
|
end
|
41
|
-
|
39
|
+
|
42
40
|
def token_class
|
43
|
-
@klass ||= eval(options[:token_class])
|
41
|
+
@klass ||= eval(options[:token_class]) # rubocop:disable Eval
|
44
42
|
end
|
45
|
-
|
43
|
+
|
46
44
|
def verify_token(token)
|
47
|
-
|
45
|
+
token = token_class.verify(token)
|
46
|
+
if token
|
48
47
|
if token.respond_to?(:expired?) && token.expired?
|
49
48
|
error_out(401, 'expired_token')
|
50
49
|
else
|
@@ -58,15 +57,14 @@ module Grape::Middleware::Auth
|
|
58
57
|
error_out(401, 'invalid_token')
|
59
58
|
end
|
60
59
|
end
|
61
|
-
|
60
|
+
|
62
61
|
def error_out(status, error)
|
63
62
|
throw :error,
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
63
|
+
message: error,
|
64
|
+
status: status,
|
65
|
+
headers: {
|
66
|
+
'WWW-Authenticate' => "OAuth realm='#{options[:realm]}', error='#{error}'"
|
67
|
+
}
|
69
68
|
end
|
70
69
|
end
|
71
70
|
end
|
72
|
-
|
@@ -6,15 +6,15 @@ module Grape
|
|
6
6
|
|
7
7
|
def default_options
|
8
8
|
{
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
9
|
+
default_status: 403, # default status returned on error
|
10
|
+
default_message: "",
|
11
|
+
format: :txt,
|
12
|
+
formatters: {},
|
13
|
+
error_formatters: {},
|
14
|
+
rescue_all: false, # true to rescue all exceptions
|
15
|
+
rescue_options: { backtrace: false }, # true to display backtrace
|
16
|
+
rescue_handlers: {}, # rescue handler blocks
|
17
|
+
rescued_errors: []
|
18
18
|
}
|
19
19
|
end
|
20
20
|
|
@@ -22,13 +22,13 @@ module Grape
|
|
22
22
|
@env = env
|
23
23
|
|
24
24
|
begin
|
25
|
-
error_response(catch(:error)
|
25
|
+
error_response(catch(:error) do
|
26
26
|
return @app.call(@env)
|
27
|
-
|
28
|
-
rescue
|
27
|
+
end)
|
28
|
+
rescue StandardError => e
|
29
29
|
is_rescuable = rescuable?(e.class)
|
30
30
|
if e.is_a?(Grape::Exceptions::Base) && !is_rescuable
|
31
|
-
handler = lambda {|
|
31
|
+
handler = lambda { |arg| error_response(arg) }
|
32
32
|
else
|
33
33
|
raise unless is_rescuable
|
34
34
|
handler = options[:rescue_handlers][e.class] || options[:rescue_handlers][:all]
|
@@ -44,33 +44,33 @@ module Grape
|
|
44
44
|
|
45
45
|
def exec_handler(e, &handler)
|
46
46
|
if handler.lambda? && handler.arity == 0
|
47
|
-
|
47
|
+
instance_exec(&handler)
|
48
48
|
else
|
49
|
-
|
49
|
+
instance_exec(e, &handler)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
53
|
def handle_error(e)
|
54
|
-
error_response(
|
54
|
+
error_response(message: e.message, backtrace: e.backtrace)
|
55
55
|
end
|
56
56
|
|
57
57
|
def error_response(error = {})
|
58
58
|
status = error[:status] || options[:default_status]
|
59
59
|
message = error[:message] || options[:default_message]
|
60
|
-
headers = {'Content-Type' => content_type}
|
60
|
+
headers = { 'Content-Type' => content_type }
|
61
61
|
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
62
62
|
backtrace = error[:backtrace] || []
|
63
63
|
rack_response(format_message(message, backtrace), status, headers)
|
64
64
|
end
|
65
65
|
|
66
66
|
def rack_response(message, status = options[:default_status], headers = { 'Content-Type' => content_type })
|
67
|
-
Rack::Response.new([
|
67
|
+
Rack::Response.new([message], status, headers).finish
|
68
68
|
end
|
69
69
|
|
70
70
|
def format_message(message, backtrace)
|
71
71
|
format = env['api.format'] || options[:format]
|
72
72
|
formatter = Grape::ErrorFormatter::Base.formatter_for(format, options)
|
73
|
-
throw :error, :
|
73
|
+
throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
|
74
74
|
formatter.call(message, backtrace, options, env)
|
75
75
|
end
|
76
76
|
|
@@ -3,14 +3,14 @@ module Grape
|
|
3
3
|
# This is a simple middleware for adding before and after filters
|
4
4
|
# to Grape APIs. It is used like so:
|
5
5
|
#
|
6
|
-
# use Grape::Middleware::Filter, :
|
6
|
+
# use Grape::Middleware::Filter, before: lambda { do_something }, after: lambda { do_something }
|
7
7
|
class Filter < Base
|
8
8
|
def before
|
9
|
-
app.instance_eval
|
9
|
+
app.instance_eval(&options[:before]) if options[:before]
|
10
10
|
end
|
11
11
|
|
12
12
|
def after
|
13
|
-
app.instance_eval
|
13
|
+
app.instance_eval(&options[:after]) if options[:after]
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -6,14 +6,17 @@ module Grape
|
|
6
6
|
|
7
7
|
def default_options
|
8
8
|
{
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
9
|
+
default_format: :txt,
|
10
|
+
formatters: {},
|
11
|
+
parsers: {}
|
12
12
|
}
|
13
13
|
end
|
14
14
|
|
15
15
|
def headers
|
16
|
-
env.dup.inject({})
|
16
|
+
env.dup.inject({}) do |h, (k, v)|
|
17
|
+
h[k.to_s.downcase[5..-1]] = v if k.to_s.downcase.start_with?('http_')
|
18
|
+
h
|
19
|
+
end
|
17
20
|
end
|
18
21
|
|
19
22
|
def before
|
@@ -31,7 +34,7 @@ module Grape
|
|
31
34
|
formatter.call body, env
|
32
35
|
end
|
33
36
|
rescue Grape::Exceptions::InvalidFormatter => e
|
34
|
-
throw :error, :
|
37
|
+
throw :error, status: 500, message: e.message
|
35
38
|
end
|
36
39
|
headers['Content-Type'] = content_type_for(env['api.format']) unless headers['Content-Type']
|
37
40
|
Rack::Response.new(bodymap, status, headers).to_a
|
@@ -42,8 +45,8 @@ module Grape
|
|
42
45
|
# store read input in env['api.request.input']
|
43
46
|
def read_body_input
|
44
47
|
if (request.post? || request.put? || request.patch? || request.delete?) &&
|
45
|
-
(!
|
46
|
-
(!
|
48
|
+
(!request.form_data? || !request.media_type) &&
|
49
|
+
(!request.parseable_data?) &&
|
47
50
|
(request.content_length.to_i > 0 || request.env['HTTP_TRANSFER_ENCODING'] == 'chunked')
|
48
51
|
|
49
52
|
if (input = env['rack.input'])
|
@@ -68,19 +71,21 @@ module Grape
|
|
68
71
|
begin
|
69
72
|
body = (env['api.request.body'] = parser.call(body, env))
|
70
73
|
if body.is_a?(Hash)
|
71
|
-
|
72
|
-
env['rack.request.form_hash'].merge(body)
|
73
|
-
|
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
|
74
79
|
env['rack.request.form_input'] = env['rack.input']
|
75
80
|
end
|
76
|
-
rescue
|
77
|
-
throw :error, :
|
81
|
+
rescue StandardError => e
|
82
|
+
throw :error, status: 400, message: e.message
|
78
83
|
end
|
79
84
|
else
|
80
85
|
env['api.request.body'] = body
|
81
86
|
end
|
82
87
|
else
|
83
|
-
throw :error, :
|
88
|
+
throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
|
84
89
|
end
|
85
90
|
end
|
86
91
|
|
@@ -89,7 +94,7 @@ module Grape
|
|
89
94
|
if content_type_for(fmt)
|
90
95
|
env['api.format'] = fmt
|
91
96
|
else
|
92
|
-
throw :error, :
|
97
|
+
throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
|
93
98
|
end
|
94
99
|
end
|
95
100
|
|
@@ -113,15 +118,15 @@ module Grape
|
|
113
118
|
|
114
119
|
def format_from_header
|
115
120
|
mime_array.each do |t|
|
116
|
-
if mime_types.key?(t)
|
117
|
-
return mime_types[t]
|
118
|
-
end
|
121
|
+
return mime_types[t] if mime_types.key?(t)
|
119
122
|
end
|
120
123
|
nil
|
121
124
|
end
|
122
125
|
|
123
126
|
def mime_array
|
124
|
-
accept = headers['accept']
|
127
|
+
accept = headers['accept']
|
128
|
+
return [] unless accept
|
129
|
+
|
125
130
|
accept_into_mime_and_quality = %r(
|
126
131
|
(
|
127
132
|
\w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
|
@@ -129,12 +134,13 @@ module Grape
|
|
129
134
|
(?:;[^,]*?)? # optionally multiple formats in a row
|
130
135
|
;\s*q=([\d.]+) # optional "quality" preference (eg q=0.5)
|
131
136
|
)?
|
132
|
-
)x
|
133
|
-
|
137
|
+
)x
|
138
|
+
|
139
|
+
vendor_prefix_pattern = /vnd\.[^+]+\+/
|
134
140
|
|
135
|
-
accept.scan(accept_into_mime_and_quality)
|
136
|
-
sort_by { |_, quality_preference| -quality_preference.to_f }
|
137
|
-
map {|mime, _| mime.sub(vendor_prefix_pattern, '') }
|
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, '') }
|
138
144
|
end
|
139
145
|
|
140
146
|
end
|