grape 1.3.2 → 1.5.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -2
- data/LICENSE +1 -1
- data/README.md +120 -24
- data/UPGRADING.md +220 -39
- data/lib/grape.rb +3 -2
- data/lib/grape/api.rb +3 -3
- data/lib/grape/api/instance.rb +22 -25
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/helpers.rb +1 -0
- data/lib/grape/dsl/inside_route.rb +70 -37
- data/lib/grape/dsl/parameters.rb +8 -4
- data/lib/grape/dsl/routing.rb +6 -7
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +8 -6
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/base.rb +3 -2
- data/lib/grape/middleware/error.rb +11 -13
- data/lib/grape/middleware/formatter.rb +3 -3
- data/lib/grape/middleware/stack.rb +8 -1
- data/lib/grape/request.rb +1 -1
- data/lib/grape/router.rb +25 -39
- data/lib/grape/router/attribute_translator.rb +26 -5
- data/lib/grape/router/route.rb +1 -19
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
- data/lib/grape/util/base_inheritable.rb +2 -2
- data/lib/grape/util/lazy_value.rb +1 -0
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +9 -7
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/types/array_coercer.rb +14 -5
- data/lib/grape/validations/types/build_coercer.rb +1 -5
- data/lib/grape/validations/types/custom_type_coercer.rb +15 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/primitive_coercer.rb +9 -3
- data/lib/grape/validations/types/set_coercer.rb +6 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/as.rb +1 -1
- data/lib/grape/validations/validators/base.rb +8 -8
- data/lib/grape/validations/validators/coerce.rb +8 -14
- data/lib/grape/validations/validators/default.rb +3 -5
- data/lib/grape/validations/validators/except_values.rb +1 -1
- data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
- data/lib/grape/validations/validators/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/instance_spec.rb +50 -0
- data/spec/grape/api_remount_spec.rb +9 -4
- data/spec/grape/api_spec.rb +75 -0
- data/spec/grape/dsl/inside_route_spec.rb +182 -33
- data/spec/grape/endpoint/declared_spec.rb +601 -0
- data/spec/grape/endpoint_spec.rb +0 -521
- data/spec/grape/entity_spec.rb +7 -1
- data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -1
- data/spec/grape/middleware/formatter_spec.rb +1 -1
- data/spec/grape/middleware/stack_spec.rb +1 -0
- data/spec/grape/request_spec.rb +1 -1
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
- data/spec/grape/validations/params_scope_spec.rb +26 -0
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +65 -5
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/validators/coerce_spec.rb +223 -25
- data/spec/grape/validations/validators/default_spec.rb +170 -0
- data/spec/grape/validations/validators/except_values_spec.rb +1 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +290 -18
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/shared/versioning_examples.rb +20 -20
- data/spec/spec_helper.rb +0 -10
- data/spec/support/chunks.rb +14 -0
- data/spec/support/versioned_helpers.rb +4 -6
- metadata +20 -9
@@ -17,7 +17,7 @@ module Grape
|
|
17
17
|
super(**args)
|
18
18
|
end
|
19
19
|
|
20
|
-
#
|
20
|
+
# Remove all the unnecessary stuff from Grape::Exceptions::Base like status
|
21
21
|
# and headers when converting a validation error to json or string
|
22
22
|
def as_json(*_args)
|
23
23
|
to_s
|
@@ -10,9 +10,9 @@ module Grape
|
|
10
10
|
|
11
11
|
attr_accessor :options, :app, :env
|
12
12
|
|
13
|
-
def initialize(app,
|
13
|
+
def initialize(app, *options)
|
14
14
|
@app = app
|
15
|
-
@options = options
|
15
|
+
@options = options.shift
|
16
16
|
end
|
17
17
|
|
18
18
|
def call(env)
|
@@ -23,7 +23,7 @@ module Grape
|
|
23
23
|
self.env = env
|
24
24
|
|
25
25
|
if options.key?(:type)
|
26
|
-
auth_proc
|
26
|
+
auth_proc = options[:proc]
|
27
27
|
auth_proc_context = context
|
28
28
|
|
29
29
|
strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
|
@@ -8,15 +8,16 @@ module Grape
|
|
8
8
|
include Helpers
|
9
9
|
|
10
10
|
attr_reader :app, :env, :options
|
11
|
+
|
11
12
|
TEXT_HTML = 'text/html'
|
12
13
|
|
13
14
|
include Grape::DSL::Headers
|
14
15
|
|
15
16
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
16
17
|
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
17
|
-
def initialize(app,
|
18
|
+
def initialize(app, *options)
|
18
19
|
@app = app
|
19
|
-
@options = default_options.merge(
|
20
|
+
@options = options.any? ? default_options.merge(options.shift) : default_options
|
20
21
|
@app_response = nil
|
21
22
|
end
|
22
23
|
|
@@ -19,15 +19,15 @@ module Grape
|
|
19
19
|
rescue_subclasses: true, # rescue subclasses of exceptions listed
|
20
20
|
rescue_options: {
|
21
21
|
backtrace: false, # true to display backtrace, true to let Grape handle Grape::Exceptions
|
22
|
-
original_exception: false
|
22
|
+
original_exception: false # true to display exception
|
23
23
|
},
|
24
24
|
rescue_handlers: {}, # rescue handler blocks
|
25
25
|
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
|
26
|
-
all_rescue_handler: nil
|
26
|
+
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
|
27
27
|
}
|
28
28
|
end
|
29
29
|
|
30
|
-
def initialize(app,
|
30
|
+
def initialize(app, *options)
|
31
31
|
super
|
32
32
|
self.class.send(:include, @options[:helpers]) if @options[:helpers]
|
33
33
|
end
|
@@ -38,15 +38,15 @@ module Grape
|
|
38
38
|
error_response(catch(:error) do
|
39
39
|
return @app.call(@env)
|
40
40
|
end)
|
41
|
-
rescue Exception =>
|
41
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
42
42
|
handler =
|
43
|
-
rescue_handler_for_base_only_class(
|
44
|
-
rescue_handler_for_class_or_its_ancestor(
|
45
|
-
rescue_handler_for_grape_exception(
|
46
|
-
rescue_handler_for_any_class(
|
43
|
+
rescue_handler_for_base_only_class(e.class) ||
|
44
|
+
rescue_handler_for_class_or_its_ancestor(e.class) ||
|
45
|
+
rescue_handler_for_grape_exception(e.class) ||
|
46
|
+
rescue_handler_for_any_class(e.class) ||
|
47
47
|
raise
|
48
48
|
|
49
|
-
run_rescue_handler(handler,
|
49
|
+
run_rescue_handler(handler, e)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -65,15 +65,13 @@ module Grape
|
|
65
65
|
message = error[:message] || options[:default_message]
|
66
66
|
headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }
|
67
67
|
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
68
|
-
backtrace = error[:backtrace] || error[:original_exception]
|
68
|
+
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
|
69
69
|
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
|
70
70
|
rack_response(format_message(message, backtrace, original_exception), status, headers)
|
71
71
|
end
|
72
72
|
|
73
73
|
def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
|
74
|
-
if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
|
75
|
-
message = ERB::Util.html_escape(message)
|
76
|
-
end
|
74
|
+
message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
|
77
75
|
Rack::Response.new([message], status, headers)
|
78
76
|
end
|
79
77
|
|
@@ -36,9 +36,9 @@ module Grape
|
|
36
36
|
def build_formatted_response(status, headers, bodies)
|
37
37
|
headers = ensure_content_type(headers)
|
38
38
|
|
39
|
-
if bodies.is_a?(Grape::
|
40
|
-
Grape::
|
41
|
-
resp.body = bodies.
|
39
|
+
if bodies.is_a?(Grape::ServeStream::StreamResponse)
|
40
|
+
Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp|
|
41
|
+
resp.body = bodies.stream
|
42
42
|
end
|
43
43
|
else
|
44
44
|
# Allow content-type to be explicitly overwritten
|
@@ -30,6 +30,10 @@ module Grape
|
|
30
30
|
def inspect
|
31
31
|
klass.to_s
|
32
32
|
end
|
33
|
+
|
34
|
+
def use_in(builder)
|
35
|
+
builder.use(@klass, *@args, &@block)
|
36
|
+
end
|
33
37
|
end
|
34
38
|
|
35
39
|
include Enumerable
|
@@ -62,6 +66,7 @@ module Grape
|
|
62
66
|
middleware = self.class::Middleware.new(*args, &block)
|
63
67
|
middlewares.insert(index, middleware)
|
64
68
|
end
|
69
|
+
ruby2_keywords :insert if respond_to?(:ruby2_keywords, true)
|
65
70
|
|
66
71
|
alias insert_before insert
|
67
72
|
|
@@ -69,11 +74,13 @@ module Grape
|
|
69
74
|
index = assert_index(index, :after)
|
70
75
|
insert(index + 1, *args, &block)
|
71
76
|
end
|
77
|
+
ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true)
|
72
78
|
|
73
79
|
def use(*args, &block)
|
74
80
|
middleware = self.class::Middleware.new(*args, &block)
|
75
81
|
middlewares.push(middleware)
|
76
82
|
end
|
83
|
+
ruby2_keywords :use if respond_to?(:ruby2_keywords, true)
|
77
84
|
|
78
85
|
def merge_with(middleware_specs)
|
79
86
|
middleware_specs.each do |operation, *args|
|
@@ -90,7 +97,7 @@ module Grape
|
|
90
97
|
def build(builder = Rack::Builder.new)
|
91
98
|
others.shift(others.size).each(&method(:merge_with))
|
92
99
|
middlewares.each do |m|
|
93
|
-
m.
|
100
|
+
m.use_in(builder)
|
94
101
|
end
|
95
102
|
builder
|
96
103
|
end
|
data/lib/grape/request.rb
CHANGED
data/lib/grape/router.rb
CHANGED
@@ -7,29 +7,12 @@ module Grape
|
|
7
7
|
class Router
|
8
8
|
attr_reader :map, :compiled
|
9
9
|
|
10
|
-
class Any < AttributeTranslator
|
11
|
-
attr_reader :pattern, :index
|
12
|
-
def initialize(pattern, index, **attributes)
|
13
|
-
@pattern = pattern
|
14
|
-
@index = index
|
15
|
-
super(attributes)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class NormalizePathCache < Grape::Util::Cache
|
20
|
-
def initialize
|
21
|
-
@cache = Hash.new do |h, path|
|
22
|
-
normalized_path = +"/#{path}"
|
23
|
-
normalized_path.squeeze!('/')
|
24
|
-
normalized_path.sub!(%r{/+\Z}, '')
|
25
|
-
normalized_path = '/' if normalized_path.empty?
|
26
|
-
h[path] = -normalized_path
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
10
|
def self.normalize_path(path)
|
32
|
-
|
11
|
+
path = +"/#{path}"
|
12
|
+
path.squeeze!('/')
|
13
|
+
path.sub!(%r{/+\Z}, '')
|
14
|
+
path = '/' if path == ''
|
15
|
+
path
|
33
16
|
end
|
34
17
|
|
35
18
|
def self.supported_methods
|
@@ -64,7 +47,7 @@ module Grape
|
|
64
47
|
|
65
48
|
def associate_routes(pattern, **options)
|
66
49
|
@neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
|
67
|
-
@neutral_map <<
|
50
|
+
@neutral_map << Grape::Router::AttributeTranslator.new(**options, pattern: pattern, index: @neutral_map.length)
|
68
51
|
end
|
69
52
|
|
70
53
|
def call(env)
|
@@ -108,37 +91,34 @@ module Grape
|
|
108
91
|
response = yield(input, method)
|
109
92
|
|
110
93
|
return response if response && !(cascade = cascade?(response))
|
111
|
-
|
94
|
+
last_neighbor_route = greedy_match?(input)
|
112
95
|
|
113
|
-
# If
|
96
|
+
# If last_neighbor_route exists and request method is OPTIONS,
|
114
97
|
# return response by using #call_with_allow_headers.
|
115
|
-
return call_with_allow_headers(
|
116
|
-
env,
|
117
|
-
neighbor.allow_header,
|
118
|
-
neighbor.endpoint
|
119
|
-
) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade
|
98
|
+
return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Grape::Http::Headers::OPTIONS && !cascade
|
120
99
|
|
121
100
|
route = match?(input, '*')
|
122
|
-
|
101
|
+
|
102
|
+
return last_neighbor_route.endpoint.call(env) if last_neighbor_route && cascade && route
|
123
103
|
|
124
104
|
if route
|
125
105
|
response = process_route(route, env)
|
126
106
|
return response if response && !(cascade = cascade?(response))
|
127
107
|
end
|
128
108
|
|
129
|
-
|
109
|
+
return call_with_allow_headers(env, last_neighbor_route) if !cascade && last_neighbor_route
|
110
|
+
|
111
|
+
nil
|
130
112
|
end
|
131
113
|
|
132
114
|
def process_route(route, env)
|
133
|
-
|
134
|
-
routing_args = env[Grape::Env::GRAPE_ROUTING_ARGS]
|
135
|
-
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(routing_args, route, input)
|
115
|
+
prepare_env_from_route(env, route)
|
136
116
|
route.exec(env)
|
137
117
|
end
|
138
118
|
|
139
119
|
def make_routing_args(default_args, route, input)
|
140
120
|
args = default_args || { route_info: route }
|
141
|
-
args.merge(route.params(input))
|
121
|
+
args.merge(route.params(input) || {})
|
142
122
|
end
|
143
123
|
|
144
124
|
def extract_input_and_method(env)
|
@@ -169,9 +149,15 @@ module Grape
|
|
169
149
|
@neutral_map.detect { |route| last_match["_#{route.index}"] }
|
170
150
|
end
|
171
151
|
|
172
|
-
def call_with_allow_headers(env,
|
173
|
-
env
|
174
|
-
|
152
|
+
def call_with_allow_headers(env, route)
|
153
|
+
prepare_env_from_route(env, route)
|
154
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze
|
155
|
+
route.endpoint.call(env)
|
156
|
+
end
|
157
|
+
|
158
|
+
def prepare_env_from_route(env, route)
|
159
|
+
input, = *extract_input_and_method(env)
|
160
|
+
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
|
175
161
|
end
|
176
162
|
|
177
163
|
def cascade?(response)
|
@@ -4,19 +4,40 @@ module Grape
|
|
4
4
|
class Router
|
5
5
|
# this could be an OpenStruct, but doesn't work in Ruby 2.3.0, see https://bugs.ruby-lang.org/issues/12251
|
6
6
|
class AttributeTranslator
|
7
|
-
attr_reader :attributes
|
7
|
+
attr_reader :attributes
|
8
8
|
|
9
|
-
|
9
|
+
ROUTE_ATTRIBUTES = %i[
|
10
|
+
prefix
|
11
|
+
version
|
12
|
+
settings
|
13
|
+
format
|
14
|
+
description
|
15
|
+
http_codes
|
16
|
+
headers
|
17
|
+
entity
|
18
|
+
details
|
19
|
+
requirements
|
20
|
+
request_method
|
21
|
+
namespace
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
ROUTER_ATTRIBUTES = %i[pattern index].freeze
|
25
|
+
|
26
|
+
def initialize(**attributes)
|
10
27
|
@attributes = attributes
|
11
|
-
|
12
|
-
|
28
|
+
end
|
29
|
+
|
30
|
+
(ROUTER_ATTRIBUTES + ROUTE_ATTRIBUTES).each do |attr|
|
31
|
+
define_method attr do
|
32
|
+
attributes[attr]
|
33
|
+
end
|
13
34
|
end
|
14
35
|
|
15
36
|
def to_h
|
16
37
|
attributes
|
17
38
|
end
|
18
39
|
|
19
|
-
def method_missing(method_name, *args)
|
40
|
+
def method_missing(method_name, *args)
|
20
41
|
if setter?(method_name[-1])
|
21
42
|
attributes[method_name[0..-1]] = *args
|
22
43
|
else
|
data/lib/grape/router/route.rb
CHANGED
@@ -18,6 +18,7 @@ module Grape
|
|
18
18
|
|
19
19
|
extend Forwardable
|
20
20
|
def_delegators :pattern, :path, :origin
|
21
|
+
delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes
|
21
22
|
|
22
23
|
def method_missing(method_id, *arguments)
|
23
24
|
match = ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
|
@@ -34,25 +35,6 @@ module Grape
|
|
34
35
|
ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
|
35
36
|
end
|
36
37
|
|
37
|
-
%i[
|
38
|
-
prefix
|
39
|
-
version
|
40
|
-
settings
|
41
|
-
format
|
42
|
-
description
|
43
|
-
http_codes
|
44
|
-
headers
|
45
|
-
entity
|
46
|
-
details
|
47
|
-
requirements
|
48
|
-
request_method
|
49
|
-
namespace
|
50
|
-
].each do |method_name|
|
51
|
-
define_method method_name do
|
52
|
-
attributes.public_send method_name
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
38
|
def route_method
|
57
39
|
warn_route_methods(:method, caller(1).shift, :request_method)
|
58
40
|
request_method
|
@@ -1,22 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Grape
|
4
|
-
module
|
5
|
-
# A simple class used to identify responses which represent files and do not
|
4
|
+
module ServeStream
|
5
|
+
# A simple class used to identify responses which represent streams (or files) and do not
|
6
6
|
# need to be formatted or pre-read by Rack::Response
|
7
|
-
class
|
8
|
-
attr_reader :
|
7
|
+
class StreamResponse
|
8
|
+
attr_reader :stream
|
9
9
|
|
10
|
-
# @param
|
11
|
-
def initialize(
|
12
|
-
@
|
10
|
+
# @param stream [Object]
|
11
|
+
def initialize(stream)
|
12
|
+
@stream = stream
|
13
13
|
end
|
14
14
|
|
15
15
|
# Equality provided mostly for tests.
|
16
16
|
#
|
17
17
|
# @return [Boolean]
|
18
18
|
def ==(other)
|
19
|
-
|
19
|
+
stream == other.stream
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -9,8 +9,8 @@ module Grape
|
|
9
9
|
|
10
10
|
# @param inherited_values [Object] An object implementing an interface
|
11
11
|
# of the Hash class.
|
12
|
-
def initialize(inherited_values =
|
13
|
-
@inherited_values = inherited_values
|
12
|
+
def initialize(inherited_values = nil)
|
13
|
+
@inherited_values = inherited_values || {}
|
14
14
|
@new_values = {}
|
15
15
|
end
|
16
16
|
|