grape 0.16.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Appraisals +4 -0
- data/CHANGELOG.md +54 -27
- data/Dangerfile +80 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +61 -27
- data/README.md +135 -7
- data/Rakefile +34 -30
- data/UPGRADING.md +21 -0
- data/gemfiles/rack_1.5.2.gemfile +21 -0
- data/gemfiles/rails_3.gemfile +22 -1
- data/gemfiles/rails_4.gemfile +21 -0
- data/gemfiles/rails_5.gemfile +34 -0
- data/grape.gemspec +0 -14
- data/lib/grape.rb +2 -0
- data/lib/grape/api.rb +9 -2
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +15 -17
- data/lib/grape/dsl/middleware.rb +15 -1
- data/lib/grape/dsl/parameters.rb +16 -14
- data/lib/grape/dsl/request_response.rb +24 -20
- data/lib/grape/dsl/routing.rb +11 -10
- data/lib/grape/dsl/settings.rb +16 -0
- data/lib/grape/endpoint.rb +77 -60
- data/lib/grape/exceptions/validation.rb +5 -2
- data/lib/grape/exceptions/validation_array_errors.rb +11 -0
- data/lib/grape/formatter/xml.rb +1 -1
- data/lib/grape/middleware/error.rb +34 -25
- data/lib/grape/middleware/formatter.rb +9 -9
- data/lib/grape/middleware/stack.rb +110 -0
- data/lib/grape/middleware/versioner.rb +1 -1
- data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
- data/lib/grape/middleware/versioner/header.rb +3 -3
- data/lib/grape/path.rb +10 -2
- data/lib/grape/request.rb +1 -1
- data/lib/grape/router.rb +10 -19
- data/lib/grape/router/pattern.rb +2 -2
- data/lib/grape/router/route.rb +3 -3
- data/lib/grape/util/content_types.rb +1 -1
- data/lib/grape/util/inheritable_setting.rb +7 -2
- data/lib/grape/util/reverse_stackable_values.rb +45 -0
- data/lib/grape/util/stackable_values.rb +10 -11
- data/lib/grape/validations/attributes_iterator.rb +32 -7
- data/lib/grape/validations/params_scope.rb +33 -21
- data/lib/grape/validations/types.rb +4 -4
- data/lib/grape/validations/types/build_coercer.rb +9 -1
- data/lib/grape/validations/validators/all_or_none.rb +2 -2
- data/lib/grape/validations/validators/allow_blank.rb +10 -11
- data/lib/grape/validations/validators/at_least_one_of.rb +1 -1
- data/lib/grape/validations/validators/base.rb +16 -6
- data/lib/grape/validations/validators/coerce.rb +3 -6
- data/lib/grape/validations/validators/default.rb +26 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +1 -1
- data/lib/grape/validations/validators/mutual_exclusion.rb +1 -1
- data/lib/grape/validations/validators/presence.rb +1 -1
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/validations/validators/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/custom_validations_spec.rb +3 -3
- data/spec/grape/api/parameters_modification_spec.rb +41 -0
- data/spec/grape/api_spec.rb +335 -108
- data/spec/grape/dsl/logger_spec.rb +1 -1
- data/spec/grape/dsl/middleware_spec.rb +25 -5
- data/spec/grape/dsl/request_response_spec.rb +20 -6
- data/spec/grape/dsl/validations_spec.rb +1 -1
- data/spec/grape/endpoint_spec.rb +166 -23
- data/spec/grape/entity_spec.rb +0 -2
- data/spec/grape/exceptions/body_parse_errors_spec.rb +37 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
- data/spec/grape/exceptions/validation_spec.rb +10 -0
- data/spec/grape/integration/global_namespace_function_spec.rb +1 -1
- data/spec/grape/integration/rack_spec.rb +1 -1
- data/spec/grape/middleware/base_spec.rb +1 -1
- data/spec/grape/middleware/exception_spec.rb +2 -2
- data/spec/grape/middleware/formatter_spec.rb +4 -4
- data/spec/grape/middleware/stack_spec.rb +123 -0
- data/spec/grape/middleware/versioner/header_spec.rb +6 -6
- data/spec/grape/request_spec.rb +22 -22
- data/spec/grape/util/inheritable_setting_spec.rb +23 -0
- data/spec/grape/util/reverse_stackable_values_spec.rb +131 -0
- data/spec/grape/validations/params_scope_spec.rb +88 -1
- data/spec/grape/validations/validators/allow_blank_spec.rb +5 -0
- data/spec/grape/validations/validators/coerce_spec.rb +5 -5
- data/spec/grape/validations/validators/default_spec.rb +44 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +36 -17
- data/spec/spec_helper.rb +1 -8
- data/spec/support/versioned_helpers.rb +3 -3
- metadata +13 -188
- data/gemfiles/rails_3.gemfile.lock +0 -225
- data/pkg/grape-0.16.1.gem +0 -0
- data/pkg/patch.diff +0 -24
- data/tmp/Gemfile.lock +0 -63
@@ -7,9 +7,12 @@ module Grape
|
|
7
7
|
attr_accessor :message_key
|
8
8
|
|
9
9
|
def initialize(args = {})
|
10
|
-
|
10
|
+
raise 'Params are missing:' unless args.key? :params
|
11
11
|
@params = args[:params]
|
12
|
-
|
12
|
+
if args.key?(:message)
|
13
|
+
@message_key = args[:message] if args[:message].is_a?(Symbol)
|
14
|
+
args[:message] = translate_message(args[:message])
|
15
|
+
end
|
13
16
|
super
|
14
17
|
end
|
15
18
|
|
data/lib/grape/formatter/xml.rb
CHANGED
@@ -8,29 +8,34 @@ module Grape
|
|
8
8
|
default_status: 500, # default status returned on error
|
9
9
|
default_message: '',
|
10
10
|
format: :txt,
|
11
|
+
helpers: nil,
|
11
12
|
formatters: {},
|
12
13
|
error_formatters: {},
|
13
14
|
rescue_all: false, # true to rescue all exceptions
|
15
|
+
rescue_grape_exceptions: false,
|
14
16
|
rescue_subclasses: true, # rescue subclasses of exceptions listed
|
15
|
-
rescue_options: { backtrace: false }, # true to display backtrace
|
17
|
+
rescue_options: { backtrace: false }, # true to display backtrace, true to let Grape handle Grape::Exceptions
|
16
18
|
rescue_handlers: {}, # rescue handler blocks
|
17
19
|
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
|
18
20
|
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
|
19
21
|
}
|
20
22
|
end
|
21
23
|
|
24
|
+
def initialize(app, options = {})
|
25
|
+
super
|
26
|
+
self.class.send(:include, @options[:helpers]) if @options[:helpers]
|
27
|
+
end
|
28
|
+
|
22
29
|
def call!(env)
|
23
30
|
@env = env
|
24
31
|
|
25
|
-
inject_helpers!
|
26
|
-
|
27
32
|
begin
|
28
33
|
error_response(catch(:error) do
|
29
34
|
return @app.call(@env)
|
30
35
|
end)
|
31
36
|
rescue StandardError => e
|
32
37
|
is_rescuable = rescuable?(e.class)
|
33
|
-
if e.is_a?(Grape::Exceptions::Base) && !is_rescuable
|
38
|
+
if e.is_a?(Grape::Exceptions::Base) && (!is_rescuable || rescuable_by_grape?(e.class))
|
34
39
|
handler = ->(arg) { error_response(arg) }
|
35
40
|
else
|
36
41
|
raise unless is_rescuable
|
@@ -45,20 +50,23 @@ module Grape
|
|
45
50
|
handler = options[:rescue_handlers].find(-> { [] }) { |error, _| klass <= error }[1]
|
46
51
|
handler ||= options[:base_only_rescue_handlers][klass]
|
47
52
|
handler ||= options[:all_rescue_handler]
|
48
|
-
|
49
|
-
if
|
50
|
-
|
51
|
-
|
52
|
-
else
|
53
|
-
fail NoMethodError, "undefined method `#{with_option}'"
|
54
|
-
end
|
53
|
+
|
54
|
+
if handler.instance_of?(Symbol)
|
55
|
+
raise NoMethodError, "undefined method `#{handler}'" unless respond_to?(handler)
|
56
|
+
handler = self.class.instance_method(handler).bind(self)
|
55
57
|
end
|
58
|
+
|
56
59
|
handler
|
57
60
|
end
|
58
61
|
|
59
62
|
def rescuable?(klass)
|
60
63
|
return false if klass == Grape::Exceptions::InvalidVersionHeader
|
61
|
-
|
64
|
+
rescue_all? || rescue_class_or_its_ancestor?(klass) || rescue_with_base_only_handler?(klass)
|
65
|
+
end
|
66
|
+
|
67
|
+
def rescuable_by_grape?(klass)
|
68
|
+
return false if klass == Grape::Exceptions::InvalidVersionHeader
|
69
|
+
options[:rescue_grape_exceptions]
|
62
70
|
end
|
63
71
|
|
64
72
|
def exec_handler(e, &handler)
|
@@ -69,19 +77,6 @@ module Grape
|
|
69
77
|
end
|
70
78
|
end
|
71
79
|
|
72
|
-
def inject_helpers!
|
73
|
-
return if helpers_available?
|
74
|
-
endpoint = @env['api.endpoint']
|
75
|
-
self.class.instance_eval do
|
76
|
-
include endpoint.send(:helpers)
|
77
|
-
end if endpoint.is_a?(Grape::Endpoint)
|
78
|
-
@helpers = true
|
79
|
-
end
|
80
|
-
|
81
|
-
def helpers_available?
|
82
|
-
@helpers
|
83
|
-
end
|
84
|
-
|
85
80
|
def error!(message, status = options[:default_status], headers = {}, backtrace = [])
|
86
81
|
headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
|
87
82
|
rack_response(format_message(message, backtrace), status, headers)
|
@@ -111,6 +106,20 @@ module Grape
|
|
111
106
|
throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
|
112
107
|
formatter.call(message, backtrace, options, env)
|
113
108
|
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def rescue_all?
|
113
|
+
options[:rescue_all]
|
114
|
+
end
|
115
|
+
|
116
|
+
def rescue_class_or_its_ancestor?(klass)
|
117
|
+
(options[:rescue_handlers] || []).any? { |error, _handler| klass <= error }
|
118
|
+
end
|
119
|
+
|
120
|
+
def rescue_with_base_only_handler?(klass)
|
121
|
+
(options[:base_only_rescue_handlers] || []).include?(klass)
|
122
|
+
end
|
114
123
|
end
|
115
124
|
end
|
116
125
|
end
|
@@ -74,7 +74,7 @@ module Grape
|
|
74
74
|
return unless
|
75
75
|
(request.post? || request.put? || request.patch? || request.delete?) &&
|
76
76
|
(!request.form_data? || !request.media_type) &&
|
77
|
-
|
77
|
+
!request.parseable_data? &&
|
78
78
|
(request.content_length.to_i > 0 || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
|
79
79
|
|
80
80
|
return unless (input = env[Grape::Env::RACK_INPUT])
|
@@ -82,7 +82,7 @@ module Grape
|
|
82
82
|
input.rewind
|
83
83
|
body = env[Grape::Env::API_REQUEST_INPUT] = input.read
|
84
84
|
begin
|
85
|
-
read_rack_input(body) if body && body.
|
85
|
+
read_rack_input(body) if body && !body.empty?
|
86
86
|
ensure
|
87
87
|
input.rewind
|
88
88
|
end
|
@@ -98,11 +98,11 @@ module Grape
|
|
98
98
|
begin
|
99
99
|
body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
|
100
100
|
if body.is_a?(Hash)
|
101
|
-
if env[Grape::Env::RACK_REQUEST_FORM_HASH]
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
101
|
+
env[Grape::Env::RACK_REQUEST_FORM_HASH] = if env[Grape::Env::RACK_REQUEST_FORM_HASH]
|
102
|
+
env[Grape::Env::RACK_REQUEST_FORM_HASH].merge(body)
|
103
|
+
else
|
104
|
+
body
|
105
|
+
end
|
106
106
|
env[Grape::Env::RACK_REQUEST_FORM_INPUT] = env[Grape::Env::RACK_INPUT]
|
107
107
|
end
|
108
108
|
rescue Grape::Exceptions::Base => e
|
@@ -168,8 +168,8 @@ module Grape
|
|
168
168
|
vendor_prefix_pattern = /vnd\.[^+]+\+/
|
169
169
|
|
170
170
|
accept.scan(accept_into_mime_and_quality)
|
171
|
-
|
172
|
-
|
171
|
+
.sort_by { |_, quality_preference| -quality_preference.to_f }
|
172
|
+
.flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
|
173
173
|
end
|
174
174
|
end
|
175
175
|
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Grape
|
2
|
+
module Middleware
|
3
|
+
# Class to handle the stack of middlewares based on ActionDispatch::MiddlewareStack
|
4
|
+
# It allows to insert and insert after
|
5
|
+
class Stack
|
6
|
+
class Middleware
|
7
|
+
attr_reader :args, :block, :klass
|
8
|
+
|
9
|
+
def initialize(klass, *args, &block)
|
10
|
+
@klass = klass
|
11
|
+
@args = args
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
klass.name
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
case other
|
21
|
+
when Middleware
|
22
|
+
klass == other.klass
|
23
|
+
when Class
|
24
|
+
klass == other
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
klass.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
include Enumerable
|
34
|
+
|
35
|
+
attr_accessor :middlewares, :others
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@middlewares = []
|
39
|
+
@others = []
|
40
|
+
end
|
41
|
+
|
42
|
+
def each
|
43
|
+
@middlewares.each { |x| yield x }
|
44
|
+
end
|
45
|
+
|
46
|
+
def size
|
47
|
+
middlewares.size
|
48
|
+
end
|
49
|
+
|
50
|
+
def last
|
51
|
+
middlewares.last
|
52
|
+
end
|
53
|
+
|
54
|
+
def [](i)
|
55
|
+
middlewares[i]
|
56
|
+
end
|
57
|
+
|
58
|
+
def insert(index, *args, &block)
|
59
|
+
index = assert_index(index, :before)
|
60
|
+
middleware = self.class::Middleware.new(*args, &block)
|
61
|
+
middlewares.insert(index, middleware)
|
62
|
+
end
|
63
|
+
|
64
|
+
alias insert_before insert
|
65
|
+
|
66
|
+
def insert_after(index, *args, &block)
|
67
|
+
index = assert_index(index, :after)
|
68
|
+
insert(index + 1, *args, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def use(*args, &block)
|
72
|
+
middleware = self.class::Middleware.new(*args, &block)
|
73
|
+
middlewares.push(middleware)
|
74
|
+
end
|
75
|
+
|
76
|
+
def merge_with(middleware_specs)
|
77
|
+
middleware_specs.each do |operation, *args|
|
78
|
+
if args.last.is_a?(Proc)
|
79
|
+
public_send(operation, *args, &args.pop)
|
80
|
+
else
|
81
|
+
public_send(operation, *args)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [Rack::Builder] the builder object with our middlewares applied
|
87
|
+
def build(builder = Rack::Builder.new)
|
88
|
+
others.shift(others.size).each(&method(:merge_with))
|
89
|
+
middlewares.each do |m|
|
90
|
+
m.block ? builder.use(m.klass, *m.args, &m.block) : builder.use(m.klass, *m.args)
|
91
|
+
end
|
92
|
+
builder
|
93
|
+
end
|
94
|
+
|
95
|
+
# @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later
|
96
|
+
# @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
|
97
|
+
def concat(other_specs)
|
98
|
+
@others << Array(other_specs).reject { |o| o.first == :use }
|
99
|
+
merge_with Array(other_specs).select { |o| o.first == :use }
|
100
|
+
end
|
101
|
+
|
102
|
+
protected
|
103
|
+
|
104
|
+
def assert_index(index, where)
|
105
|
+
i = index.is_a?(Integer) ? index : middlewares.index(index)
|
106
|
+
i || raise("No such middleware to insert #{where}: #{index.inspect}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -50,7 +50,7 @@ module Grape
|
|
50
50
|
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
51
51
|
def cascade?
|
52
52
|
if options[:version_options] && options[:version_options].key?(:cascade)
|
53
|
-
|
53
|
+
options[:version_options][:cascade]
|
54
54
|
else
|
55
55
|
true
|
56
56
|
end
|
@@ -87,12 +87,12 @@ module Grape
|
|
87
87
|
end
|
88
88
|
|
89
89
|
def fail_with_invalid_accept_header!(message)
|
90
|
-
|
90
|
+
raise Grape::Exceptions::InvalidAcceptHeader
|
91
91
|
.new(message, error_headers)
|
92
92
|
end
|
93
93
|
|
94
94
|
def fail_with_invalid_version_header!(message)
|
95
|
-
|
95
|
+
raise Grape::Exceptions::InvalidVersionHeader
|
96
96
|
.new(message, error_headers)
|
97
97
|
end
|
98
98
|
|
@@ -159,7 +159,7 @@ module Grape
|
|
159
159
|
# header, one can set the `:cascade` option to `false`.
|
160
160
|
def cascade?
|
161
161
|
if version_options && version_options.key?(:cascade)
|
162
|
-
|
162
|
+
version_options[:cascade]
|
163
163
|
else
|
164
164
|
true
|
165
165
|
end
|
data/lib/grape/path.rb
CHANGED
@@ -22,11 +22,19 @@ module Grape
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def uses_specific_format?
|
25
|
-
|
25
|
+
if settings.key?(:format) && settings.key?(:content_types)
|
26
|
+
(settings[:format] && Array(settings[:content_types]).size == 1)
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
26
30
|
end
|
27
31
|
|
28
32
|
def uses_path_versioning?
|
29
|
-
|
33
|
+
if settings.key?(:version) && settings[:version_options] && settings[:version_options].key?(:using)
|
34
|
+
(settings[:version] && settings[:version_options][:using] == :path)
|
35
|
+
else
|
36
|
+
false
|
37
|
+
end
|
30
38
|
end
|
31
39
|
|
32
40
|
def namespace?
|
data/lib/grape/request.rb
CHANGED
data/lib/grape/router.rb
CHANGED
@@ -11,6 +11,14 @@ module Grape
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
def self.normalize_path(path)
|
15
|
+
path = "/#{path}"
|
16
|
+
path.squeeze!('/')
|
17
|
+
path.sub!(%r{/+\Z}, '')
|
18
|
+
path = '/' if path == ''
|
19
|
+
path
|
20
|
+
end
|
21
|
+
|
14
22
|
def initialize
|
15
23
|
@neutral_map = []
|
16
24
|
@map = Hash.new { |hash, key| hash[key] = [] }
|
@@ -125,17 +133,8 @@ module Grape
|
|
125
133
|
end
|
126
134
|
|
127
135
|
def method_not_allowed(env, methods, endpoint)
|
128
|
-
env[Grape::Env::GRAPE_METHOD_NOT_ALLOWED] =
|
129
|
-
|
130
|
-
current.instance_eval do
|
131
|
-
@lazy_initialized = false
|
132
|
-
lazy_initialize!
|
133
|
-
run_filters befores, :before
|
134
|
-
@block = proc do
|
135
|
-
fail Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => methods)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
current.call(env)
|
136
|
+
env[Grape::Env::GRAPE_METHOD_NOT_ALLOWED] = methods
|
137
|
+
endpoint.call(env)
|
139
138
|
end
|
140
139
|
|
141
140
|
def cascade?(response)
|
@@ -149,13 +148,5 @@ module Grape
|
|
149
148
|
def string_for(input)
|
150
149
|
self.class.normalize_path(input)
|
151
150
|
end
|
152
|
-
|
153
|
-
def self.normalize_path(path)
|
154
|
-
path = "/#{path}"
|
155
|
-
path.squeeze!('/')
|
156
|
-
path.sub!(%r{/+\Z}, '')
|
157
|
-
path = '/' if path == ''
|
158
|
-
path
|
159
|
-
end
|
160
151
|
end
|
161
152
|
end
|
data/lib/grape/router/pattern.rb
CHANGED
@@ -12,7 +12,7 @@ module Grape
|
|
12
12
|
extend Forwardable
|
13
13
|
def_delegators :pattern, :named_captures, :params
|
14
14
|
def_delegators :@regexp, :===
|
15
|
-
|
15
|
+
alias match? ===
|
16
16
|
|
17
17
|
def initialize(pattern, options = {})
|
18
18
|
@origin = pattern
|
@@ -30,7 +30,7 @@ module Grape
|
|
30
30
|
|
31
31
|
def pattern_options
|
32
32
|
options = DEFAULT_PATTERN_OPTIONS.dup
|
33
|
-
options
|
33
|
+
options[:capture] = capture if capture.present?
|
34
34
|
options
|
35
35
|
end
|
36
36
|
|