grape 2.3.0 → 3.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/CONTRIBUTING.md +2 -10
- data/README.md +106 -43
- data/UPGRADING.md +90 -1
- data/grape.gemspec +4 -4
- data/lib/grape/api/instance.rb +51 -73
- data/lib/grape/api.rb +56 -89
- data/lib/grape/cookies.rb +31 -25
- data/lib/grape/dry_types.rb +48 -4
- data/lib/grape/dsl/callbacks.rb +8 -58
- data/lib/grape/dsl/desc.rb +8 -67
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/helpers.rb +60 -65
- data/lib/grape/dsl/inside_route.rb +26 -61
- data/lib/grape/dsl/logger.rb +3 -6
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +10 -19
- data/lib/grape/dsl/request_response.rb +136 -139
- data/lib/grape/dsl/routing.rb +230 -194
- data/lib/grape/dsl/settings.rb +22 -134
- data/lib/grape/dsl/validations.rb +37 -45
- data/lib/grape/endpoint.rb +91 -126
- data/lib/grape/error_formatter/base.rb +2 -0
- data/lib/grape/exceptions/base.rb +1 -1
- data/lib/grape/exceptions/conflicting_types.rb +11 -0
- data/lib/grape/exceptions/invalid_parameters.rb +11 -0
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
- data/lib/grape/extensions/hash.rb +2 -1
- data/lib/grape/extensions/hashie/mash.rb +3 -5
- data/lib/grape/locale/en.yml +44 -44
- data/lib/grape/middleware/auth/base.rb +11 -32
- data/lib/grape/middleware/auth/dsl.rb +22 -29
- data/lib/grape/middleware/base.rb +30 -11
- data/lib/grape/middleware/error.rb +14 -32
- data/lib/grape/middleware/formatter.rb +40 -72
- data/lib/grape/middleware/stack.rb +28 -38
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -4
- data/lib/grape/middleware/versioner/base.rb +30 -56
- data/lib/grape/middleware/versioner/header.rb +2 -2
- data/lib/grape/middleware/versioner/param.rb +2 -3
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +11 -0
- data/lib/grape/params_builder/base.rb +20 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +32 -0
- data/lib/grape/request.rb +161 -22
- data/lib/grape/router/route.rb +1 -1
- data/lib/grape/router.rb +27 -8
- data/lib/grape/util/api_description.rb +56 -0
- data/lib/grape/util/base_inheritable.rb +5 -2
- data/lib/grape/util/inheritable_setting.rb +7 -0
- data/lib/grape/util/media_type.rb +1 -1
- data/lib/grape/util/registry.rb +1 -1
- data/lib/grape/validations/contract_scope.rb +2 -2
- data/lib/grape/validations/params_documentation.rb +50 -0
- data/lib/grape/validations/params_scope.rb +46 -56
- data/lib/grape/validations/types/array_coercer.rb +2 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
- data/lib/grape/validations/types/primitive_coercer.rb +1 -28
- data/lib/grape/validations/types.rb +10 -25
- data/lib/grape/validations/validators/base.rb +2 -9
- data/lib/grape/validations/validators/except_values_validator.rb +1 -1
- data/lib/grape/validations/validators/presence_validator.rb +1 -1
- data/lib/grape/validations/validators/regexp_validator.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +18 -9
- metadata +35 -20
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -19
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/error_formatter/jsonapi.rb +0 -7
- data/lib/grape/http/headers.rb +0 -56
- data/lib/grape/middleware/helpers.rb +0 -12
- data/lib/grape/parser/jsonapi.rb +0 -7
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/lazy/object.rb +0 -45
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/validations/attributes_doc.rb +0 -60
data/lib/grape/api/instance.rb
CHANGED
|
@@ -5,14 +5,30 @@ module Grape
|
|
|
5
5
|
# The API Instance class, is the engine behind Grape::API. Each class that inherits
|
|
6
6
|
# from this will represent a different API instance
|
|
7
7
|
class Instance
|
|
8
|
-
|
|
8
|
+
extend Grape::DSL::Settings
|
|
9
|
+
extend Grape::DSL::Desc
|
|
10
|
+
extend Grape::DSL::Validations
|
|
11
|
+
extend Grape::DSL::Callbacks
|
|
12
|
+
extend Grape::DSL::Logger
|
|
13
|
+
extend Grape::DSL::Middleware
|
|
14
|
+
extend Grape::DSL::RequestResponse
|
|
15
|
+
extend Grape::DSL::Routing
|
|
16
|
+
extend Grape::DSL::Helpers
|
|
17
|
+
extend Grape::Middleware::Auth::DSL
|
|
18
|
+
|
|
19
|
+
Boolean = Grape::API::Boolean
|
|
9
20
|
|
|
10
21
|
class << self
|
|
22
|
+
extend Forwardable
|
|
11
23
|
attr_reader :instance, :base
|
|
12
24
|
attr_accessor :configuration
|
|
13
25
|
|
|
26
|
+
def_delegators :base, :to_s
|
|
27
|
+
|
|
14
28
|
def given(conditional_option, &block)
|
|
15
|
-
|
|
29
|
+
return unless conditional_option
|
|
30
|
+
|
|
31
|
+
mounted(&block)
|
|
16
32
|
end
|
|
17
33
|
|
|
18
34
|
def mounted(&block)
|
|
@@ -24,10 +40,6 @@ module Grape
|
|
|
24
40
|
grape_api.instances << self
|
|
25
41
|
end
|
|
26
42
|
|
|
27
|
-
def to_s
|
|
28
|
-
base&.to_s || super
|
|
29
|
-
end
|
|
30
|
-
|
|
31
43
|
def base_instance?
|
|
32
44
|
self == base.base_instance
|
|
33
45
|
end
|
|
@@ -49,11 +61,6 @@ module Grape
|
|
|
49
61
|
@instance ||= new # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
50
62
|
end
|
|
51
63
|
|
|
52
|
-
# Wipe the compiled API so we can recompile after changes were made.
|
|
53
|
-
def change!
|
|
54
|
-
@instance = nil
|
|
55
|
-
end
|
|
56
|
-
|
|
57
64
|
# This is the interface point between Rack and Grape; it accepts a request
|
|
58
65
|
# from Rack and ultimately returns an array of three values: the status,
|
|
59
66
|
# the headers, and the body. See [the rack specification]
|
|
@@ -70,11 +77,9 @@ module Grape
|
|
|
70
77
|
|
|
71
78
|
# (see #cascade?)
|
|
72
79
|
def cascade(value = nil)
|
|
73
|
-
if value.nil?
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
namespace_inheritable(:cascade, value)
|
|
77
|
-
end
|
|
80
|
+
return inheritable_setting.namespace_inheritable.key?(:cascade) ? !inheritable_setting.namespace_inheritable(:cascade).nil? : true if value.nil?
|
|
81
|
+
|
|
82
|
+
inheritable_setting.namespace_inheritable[:cascade] = value
|
|
78
83
|
end
|
|
79
84
|
|
|
80
85
|
def compile!
|
|
@@ -91,45 +96,6 @@ module Grape
|
|
|
91
96
|
|
|
92
97
|
protected
|
|
93
98
|
|
|
94
|
-
def prepare_routes
|
|
95
|
-
endpoints.map(&:routes).flatten
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Execute first the provided block, then each of the
|
|
99
|
-
# block passed in. Allows for simple 'before' setups
|
|
100
|
-
# of settings stack pushes.
|
|
101
|
-
def nest(*blocks, &block)
|
|
102
|
-
blocks.compact!
|
|
103
|
-
if blocks.any?
|
|
104
|
-
evaluate_as_instance_with_configuration(block) if block
|
|
105
|
-
blocks.each { |b| evaluate_as_instance_with_configuration(b) }
|
|
106
|
-
reset_validations!
|
|
107
|
-
else
|
|
108
|
-
instance_eval(&block)
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def evaluate_as_instance_with_configuration(block, lazy: false)
|
|
113
|
-
lazy_block = Grape::Util::Lazy::Block.new do |configuration|
|
|
114
|
-
value_for_configuration = configuration
|
|
115
|
-
self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
|
|
116
|
-
response = instance_eval(&block)
|
|
117
|
-
self.configuration = value_for_configuration
|
|
118
|
-
response
|
|
119
|
-
end
|
|
120
|
-
if base && base_instance? && lazy
|
|
121
|
-
lazy_block
|
|
122
|
-
else
|
|
123
|
-
lazy_block.evaluate_from(configuration)
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def inherited(subclass)
|
|
128
|
-
super
|
|
129
|
-
subclass.reset!
|
|
130
|
-
subclass.logger = logger.clone
|
|
131
|
-
end
|
|
132
|
-
|
|
133
99
|
def inherit_settings(other_settings)
|
|
134
100
|
top_level_setting.inherit_from other_settings.point_in_time_copy
|
|
135
101
|
|
|
@@ -142,6 +108,19 @@ module Grape
|
|
|
142
108
|
|
|
143
109
|
reset_routes!
|
|
144
110
|
end
|
|
111
|
+
|
|
112
|
+
# Wipe the compiled API so we can recompile after changes were made.
|
|
113
|
+
def change!
|
|
114
|
+
@instance = nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def inherited(subclass)
|
|
120
|
+
super
|
|
121
|
+
subclass.reset!
|
|
122
|
+
subclass.logger logger.clone
|
|
123
|
+
end
|
|
145
124
|
end
|
|
146
125
|
|
|
147
126
|
attr_reader :router
|
|
@@ -164,7 +143,7 @@ module Grape
|
|
|
164
143
|
status, headers, response = @router.call(env)
|
|
165
144
|
unless cascade?
|
|
166
145
|
headers = Grape::Util::Header.new.merge(headers)
|
|
167
|
-
headers.delete(
|
|
146
|
+
headers.delete('X-Cascade')
|
|
168
147
|
end
|
|
169
148
|
|
|
170
149
|
[status, headers, response]
|
|
@@ -179,8 +158,9 @@ module Grape
|
|
|
179
158
|
# errors from reaching upstream. This is effectivelly done by unsetting
|
|
180
159
|
# X-Cascade. Default :cascade is true.
|
|
181
160
|
def cascade?
|
|
182
|
-
|
|
183
|
-
return
|
|
161
|
+
namespace_inheritable = self.class.inheritable_setting.namespace_inheritable
|
|
162
|
+
return namespace_inheritable[:cascade] if namespace_inheritable.key?(:cascade)
|
|
163
|
+
return namespace_inheritable[:version_options][:cascade] if namespace_inheritable[:version_options]&.key?(:cascade)
|
|
184
164
|
|
|
185
165
|
true
|
|
186
166
|
end
|
|
@@ -211,11 +191,12 @@ module Grape
|
|
|
211
191
|
last_route = routes.last # Most of the configuration is taken from the last endpoint
|
|
212
192
|
next if routes.any? { |route| route.request_method == '*' }
|
|
213
193
|
|
|
194
|
+
namespace_inheritable = self.class.inheritable_setting.namespace_inheritable
|
|
214
195
|
allowed_methods = routes.map(&:request_method)
|
|
215
|
-
allowed_methods |= [Rack::HEAD] if !
|
|
196
|
+
allowed_methods |= [Rack::HEAD] if !namespace_inheritable[:do_not_route_head] && allowed_methods.include?(Rack::GET)
|
|
216
197
|
|
|
217
|
-
allow_header =
|
|
218
|
-
last_route.app.options[:options_route_enabled] = true unless
|
|
198
|
+
allow_header = namespace_inheritable[:do_not_route_options] ? allowed_methods : [Rack::OPTIONS] | allowed_methods
|
|
199
|
+
last_route.app.options[:options_route_enabled] = true unless namespace_inheritable[:do_not_route_options] || allowed_methods.include?(Rack::OPTIONS)
|
|
219
200
|
|
|
220
201
|
@router.associate_routes(last_route.pattern, {
|
|
221
202
|
endpoint: last_route.app,
|
|
@@ -224,22 +205,19 @@ module Grape
|
|
|
224
205
|
end
|
|
225
206
|
end
|
|
226
207
|
|
|
208
|
+
ROOT_PREFIX_VERSIONING_KEY = %i[version version_options root_prefix].freeze
|
|
209
|
+
private_constant :ROOT_PREFIX_VERSIONING_KEY
|
|
210
|
+
|
|
227
211
|
# Allows definition of endpoints that ignore the versioning configuration
|
|
228
212
|
# used by the rest of your API.
|
|
229
213
|
def without_root_prefix_and_versioning
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
old_root_prefix = self.class.namespace_inheritable(:root_prefix)
|
|
233
|
-
|
|
234
|
-
self.class.namespace_inheritable_to_nil(:version)
|
|
235
|
-
self.class.namespace_inheritable_to_nil(:version_options)
|
|
236
|
-
self.class.namespace_inheritable_to_nil(:root_prefix)
|
|
237
|
-
|
|
214
|
+
inheritable_setting = self.class.inheritable_setting
|
|
215
|
+
deleted_values = inheritable_setting.namespace_inheritable.delete(*ROOT_PREFIX_VERSIONING_KEY)
|
|
238
216
|
yield
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
217
|
+
ensure
|
|
218
|
+
ROOT_PREFIX_VERSIONING_KEY.each_with_index do |key, index|
|
|
219
|
+
inheritable_setting.namespace_inheritable[key] = deleted_values[index]
|
|
220
|
+
end
|
|
243
221
|
end
|
|
244
222
|
end
|
|
245
223
|
end
|
data/lib/grape/api.rb
CHANGED
|
@@ -5,7 +5,9 @@ module Grape
|
|
|
5
5
|
# should subclass this class in order to build an API.
|
|
6
6
|
class API
|
|
7
7
|
# Class methods that we want to call on the API rather than on the API object
|
|
8
|
-
NON_OVERRIDABLE = %i[call call! configuration compile! inherited].freeze
|
|
8
|
+
NON_OVERRIDABLE = %i[call call! configuration compile! inherited recognize_path routes].freeze
|
|
9
|
+
|
|
10
|
+
Helpers = Grape::DSL::Helpers::BaseHelper
|
|
9
11
|
|
|
10
12
|
class Boolean
|
|
11
13
|
def self.build(val)
|
|
@@ -15,26 +17,18 @@ module Grape
|
|
|
15
17
|
end
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
class Instance
|
|
19
|
-
Boolean = Grape::API::Boolean
|
|
20
|
-
end
|
|
21
|
-
|
|
22
20
|
class << self
|
|
21
|
+
extend Forwardable
|
|
23
22
|
attr_accessor :base_instance, :instances
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
def new(...)
|
|
27
|
-
base_instance.new(...)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# When inherited, will create a list of all instances (times the API was mounted)
|
|
31
|
-
# It will listen to the setup required to mount that endpoint, and replicate it on any new instance
|
|
32
|
-
def inherited(api)
|
|
33
|
-
super
|
|
24
|
+
delegate_missing_to :base_instance
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
# This is the interface point between Rack and Grape; it accepts a request
|
|
27
|
+
# from Rack and ultimately returns an array of three values: the status,
|
|
28
|
+
# the headers, and the body. See [the rack specification]
|
|
29
|
+
# (https://github.com/rack/rack/blob/main/SPEC.rdoc) for more.
|
|
30
|
+
# NOTE: This will only be called on an API directly mounted on RACK
|
|
31
|
+
def_delegators :base_instance, :new, :configuration, :call, :compile!
|
|
38
32
|
|
|
39
33
|
# Initialize the instance variables on the remountable class, and the base_instance
|
|
40
34
|
# an instance that will be used to create the set up but will not be mounted
|
|
@@ -48,8 +42,8 @@ module Grape
|
|
|
48
42
|
# Redefines all methods so that are forwarded to add_setup and be recorded
|
|
49
43
|
def override_all_methods!
|
|
50
44
|
(base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|
|
|
51
|
-
define_singleton_method(method_override) do |*args, &block|
|
|
52
|
-
add_setup(method_override,
|
|
45
|
+
define_singleton_method(method_override) do |*args, **kwargs, &block|
|
|
46
|
+
add_setup(method: method_override, args: args, kwargs: kwargs, block: block)
|
|
53
47
|
end
|
|
54
48
|
end
|
|
55
49
|
end
|
|
@@ -69,96 +63,69 @@ module Grape
|
|
|
69
63
|
end
|
|
70
64
|
end
|
|
71
65
|
|
|
72
|
-
# This is the interface point between Rack and Grape; it accepts a request
|
|
73
|
-
# from Rack and ultimately returns an array of three values: the status,
|
|
74
|
-
# the headers, and the body. See [the rack specification]
|
|
75
|
-
# (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
|
|
76
|
-
# NOTE: This will only be called on an API directly mounted on RACK
|
|
77
|
-
def call(...)
|
|
78
|
-
instance_for_rack.call(...)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
66
|
# The remountable class can have a configuration hash to provide some dynamic class-level variables.
|
|
82
67
|
# For instance, a description could be done using: `desc configuration[:description]` if it may vary
|
|
83
68
|
# depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
|
|
84
69
|
# too much, you may actually want to provide a new API rather than remount it.
|
|
85
|
-
def mount_instance(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
instance
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Replays the set up to produce an API as defined in this class, can be called
|
|
94
|
-
# on classes that inherit from Grape::API
|
|
95
|
-
def replay_setup_on(instance)
|
|
96
|
-
@setup.each do |setup_step|
|
|
97
|
-
replay_step_on(instance, setup_step)
|
|
70
|
+
def mount_instance(configuration: nil)
|
|
71
|
+
Class.new(@base_parent).tap do |instance|
|
|
72
|
+
instance.configuration = Grape::Util::EndpointConfiguration.new(configuration || {})
|
|
73
|
+
instance.base = self
|
|
74
|
+
replay_setup_on(instance)
|
|
98
75
|
end
|
|
99
76
|
end
|
|
100
77
|
|
|
101
|
-
|
|
102
|
-
super || base_instance.respond_to?(method, include_private)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def respond_to_missing?(method, include_private = false)
|
|
106
|
-
base_instance.respond_to?(method, include_private)
|
|
107
|
-
end
|
|
78
|
+
private
|
|
108
79
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
else
|
|
114
|
-
super
|
|
115
|
-
end
|
|
116
|
-
end
|
|
80
|
+
# When inherited, will create a list of all instances (times the API was mounted)
|
|
81
|
+
# It will listen to the setup required to mount that endpoint, and replicate it on any new instance
|
|
82
|
+
def inherited(api)
|
|
83
|
+
super
|
|
117
84
|
|
|
118
|
-
|
|
119
|
-
|
|
85
|
+
api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
|
|
86
|
+
api.override_all_methods!
|
|
120
87
|
end
|
|
121
88
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
else
|
|
128
|
-
mounted_instances.first
|
|
89
|
+
# Replays the set up to produce an API as defined in this class, can be called
|
|
90
|
+
# on classes that inherit from Grape::API
|
|
91
|
+
def replay_setup_on(instance)
|
|
92
|
+
@setup.each do |setup_step|
|
|
93
|
+
replay_step_on(instance, **setup_step)
|
|
129
94
|
end
|
|
130
95
|
end
|
|
131
96
|
|
|
132
97
|
# Adds a new stage to the set up require to get a Grape::API up and running
|
|
133
|
-
def add_setup(
|
|
134
|
-
|
|
135
|
-
@setup += [setup_step]
|
|
98
|
+
def add_setup(**step)
|
|
99
|
+
@setup << step
|
|
136
100
|
last_response = nil
|
|
137
101
|
@instances.each do |instance|
|
|
138
|
-
last_response = replay_step_on(instance,
|
|
102
|
+
last_response = replay_step_on(instance, **step)
|
|
139
103
|
end
|
|
140
104
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
105
|
+
refresh_mount_step if step[:method] != :mount
|
|
106
|
+
last_response
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Updating all previously mounted classes in the case that new methods have been executed.
|
|
110
|
+
def refresh_mount_step
|
|
111
|
+
@setup.each do |setup_step|
|
|
112
|
+
next if setup_step[:method] != :mount
|
|
113
|
+
|
|
114
|
+
refresh_mount_step = setup_step.merge(method: :refresh_mounted_api)
|
|
115
|
+
@setup << refresh_mount_step
|
|
116
|
+
@instances.each do |instance|
|
|
117
|
+
replay_step_on(instance, **refresh_mount_step)
|
|
150
118
|
end
|
|
151
119
|
end
|
|
152
|
-
|
|
153
|
-
last_response
|
|
154
120
|
end
|
|
155
121
|
|
|
156
|
-
def replay_step_on(instance,
|
|
157
|
-
return if skip_immediate_run?(instance,
|
|
122
|
+
def replay_step_on(instance, method:, args:, kwargs:, block:)
|
|
123
|
+
return if skip_immediate_run?(instance, args, kwargs)
|
|
158
124
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
125
|
+
eval_args = evaluate_arguments(instance.configuration, *args)
|
|
126
|
+
eval_kwargs = kwargs.deep_transform_values { |v| evaluate_arguments(instance.configuration, v).first }
|
|
127
|
+
response = instance.__send__(method, *eval_args, **eval_kwargs, &block)
|
|
128
|
+
if skip_immediate_run?(instance, [response], kwargs)
|
|
162
129
|
response
|
|
163
130
|
else
|
|
164
131
|
evaluate_arguments(instance.configuration, response).first
|
|
@@ -166,18 +133,18 @@ module Grape
|
|
|
166
133
|
end
|
|
167
134
|
|
|
168
135
|
# Skips steps that contain arguments to be lazily executed (on re-mount time)
|
|
169
|
-
def skip_immediate_run?(instance, args)
|
|
136
|
+
def skip_immediate_run?(instance, args, kwargs)
|
|
170
137
|
instance.base_instance? &&
|
|
171
|
-
(any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) })
|
|
138
|
+
(any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) } || any_lazy?(kwargs.values))
|
|
172
139
|
end
|
|
173
140
|
|
|
174
141
|
def any_lazy?(args)
|
|
175
|
-
args.any? { |argument| argument.
|
|
142
|
+
args.any? { |argument| argument.try(:lazy?) }
|
|
176
143
|
end
|
|
177
144
|
|
|
178
145
|
def evaluate_arguments(configuration, *args)
|
|
179
146
|
args.map do |argument|
|
|
180
|
-
if argument.
|
|
147
|
+
if argument.try(:lazy?)
|
|
181
148
|
argument.evaluate_from(configuration)
|
|
182
149
|
elsif argument.is_a?(Hash)
|
|
183
150
|
argument.transform_values { |value| evaluate_arguments(configuration, value).first }
|
data/lib/grape/cookies.rb
CHANGED
|
@@ -2,43 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
class Cookies
|
|
5
|
-
|
|
6
|
-
@cookies = {}
|
|
7
|
-
@send_cookies = {}
|
|
8
|
-
end
|
|
5
|
+
extend Forwardable
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
DELETED_COOKIES_ATTRS = {
|
|
8
|
+
max_age: '0',
|
|
9
|
+
value: '',
|
|
10
|
+
expires: Time.at(0)
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def_delegators :cookies, :[], :each
|
|
14
|
+
|
|
15
|
+
def initialize(rack_cookies)
|
|
16
|
+
@cookies = rack_cookies
|
|
17
|
+
@send_cookies = nil
|
|
14
18
|
end
|
|
15
19
|
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
def response_cookies
|
|
21
|
+
return unless @send_cookies
|
|
22
|
+
|
|
23
|
+
send_cookies.each do |name|
|
|
24
|
+
yield name, cookies[name]
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
|
|
23
|
-
def [](name)
|
|
24
|
-
|
|
28
|
+
def []=(name, value)
|
|
29
|
+
cookies[name] = value
|
|
30
|
+
send_cookies << name
|
|
25
31
|
end
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
# see https://github.com/rack/rack/blob/main/lib/rack/utils.rb#L338-L340
|
|
34
|
+
def delete(name, **opts)
|
|
35
|
+
self.[]=(name, opts.merge(DELETED_COOKIES_ATTRS))
|
|
30
36
|
end
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def cookies
|
|
41
|
+
return @cookies unless @cookies.is_a?(Proc)
|
|
42
|
+
|
|
43
|
+
@cookies = @cookies.call.with_indifferent_access
|
|
34
44
|
end
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def delete(name, **opts)
|
|
39
|
-
options = opts.merge(max_age: '0', value: '', expires: Time.at(0))
|
|
40
|
-
self.[]=(name, options)
|
|
46
|
+
def send_cookies
|
|
47
|
+
@send_cookies ||= Set.new
|
|
41
48
|
end
|
|
42
|
-
# rubocop:enable Layout/SpaceBeforeBrackets
|
|
43
49
|
end
|
|
44
50
|
end
|
data/lib/grape/dry_types.rb
CHANGED
|
@@ -2,9 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DryTypes
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
# https://dry-rb.org/gems/dry-types/main/getting-started/
|
|
6
|
+
# limit to what Grape is using
|
|
7
|
+
include Dry.Types(:params, :coercible, :strict)
|
|
8
|
+
|
|
9
|
+
class StrictCache < Grape::Util::Cache
|
|
10
|
+
MAPPING = {
|
|
11
|
+
Grape::API::Boolean => DryTypes::Strict::Bool,
|
|
12
|
+
BigDecimal => DryTypes::Strict::Decimal,
|
|
13
|
+
Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
|
|
14
|
+
TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
|
|
15
|
+
FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
super
|
|
20
|
+
@cache = Hash.new do |h, strict_type|
|
|
21
|
+
h[strict_type] = MAPPING.fetch(strict_type) do
|
|
22
|
+
DryTypes.wrapped_dry_types_const_get(DryTypes::Strict, strict_type)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ParamsCache < Grape::Util::Cache
|
|
29
|
+
MAPPING = {
|
|
30
|
+
Grape::API::Boolean => DryTypes::Params::Bool,
|
|
31
|
+
BigDecimal => DryTypes::Params::Decimal,
|
|
32
|
+
Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
|
|
33
|
+
TrueClass => DryTypes::Params::Bool.constrained(eql: true),
|
|
34
|
+
FalseClass => DryTypes::Params::Bool.constrained(eql: false),
|
|
35
|
+
String => DryTypes::Coercible::String
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
def initialize
|
|
39
|
+
super
|
|
40
|
+
@cache = Hash.new do |h, params_type|
|
|
41
|
+
h[params_type] = MAPPING.fetch(params_type) do
|
|
42
|
+
DryTypes.wrapped_dry_types_const_get(DryTypes::Params, params_type)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.wrapped_dry_types_const_get(dry_type, type)
|
|
49
|
+
dry_type.const_get(type.name, false)
|
|
50
|
+
rescue NameError
|
|
51
|
+
raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])
|
|
52
|
+
end
|
|
9
53
|
end
|
|
10
54
|
end
|
data/lib/grape/dsl/callbacks.rb
CHANGED
|
@@ -2,66 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
|
-
# Blocks can be executed before or after every API call, using `before`, `after`,
|
|
6
|
-
# `before_validation` and `after_validation`.
|
|
7
|
-
#
|
|
8
|
-
# Before and after callbacks execute in the following order:
|
|
9
|
-
#
|
|
10
|
-
# 1. `before`
|
|
11
|
-
# 2. `before_validation`
|
|
12
|
-
# 3. _validations_
|
|
13
|
-
# 4. `after_validation`
|
|
14
|
-
# 5. _the API call_
|
|
15
|
-
# 6. `after`
|
|
16
|
-
#
|
|
17
|
-
# Steps 4, 5 and 6 only happen if validation succeeds.
|
|
18
5
|
module Callbacks
|
|
19
|
-
|
|
6
|
+
# before: execute the given block before validation, coercion, or any endpoint
|
|
7
|
+
# before_validation: execute the given block after `before`, but prior to validation or coercion
|
|
8
|
+
# after_validation: execute the given block after validations and coercions, but before any endpoint code
|
|
9
|
+
# after: execute the given block after the endpoint code has run except in unsuccessful
|
|
10
|
+
# finally: execute the given block after the endpoint code even if unsuccessful
|
|
20
11
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# Execute the given block before validation, coercion, or any endpoint
|
|
25
|
-
# code is executed.
|
|
26
|
-
def before(&block)
|
|
27
|
-
namespace_stackable(:befores, block)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Execute the given block after `before`, but prior to validation or
|
|
31
|
-
# coercion.
|
|
32
|
-
def before_validation(&block)
|
|
33
|
-
namespace_stackable(:before_validations, block)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Execute the given block after validations and coercions, but before
|
|
37
|
-
# any endpoint code.
|
|
38
|
-
def after_validation(&block)
|
|
39
|
-
namespace_stackable(:after_validations, block)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Execute the given block after the endpoint code has run.
|
|
43
|
-
def after(&block)
|
|
44
|
-
namespace_stackable(:afters, block)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Allows you to specify a something that will always be executed after a call
|
|
48
|
-
# API call. Unlike the `after` block, this code will run even on
|
|
49
|
-
# unsuccesful requests.
|
|
50
|
-
# @example
|
|
51
|
-
# class ExampleAPI < Grape::API
|
|
52
|
-
# before do
|
|
53
|
-
# ApiLogger.start
|
|
54
|
-
# end
|
|
55
|
-
# finally do
|
|
56
|
-
# ApiLogger.close
|
|
57
|
-
# end
|
|
58
|
-
# end
|
|
59
|
-
#
|
|
60
|
-
# This will make sure that the ApiLogger is opened and closed around every
|
|
61
|
-
# request
|
|
62
|
-
# @param ensured_block [Proc] The block to be executed after every api_call
|
|
63
|
-
def finally(&block)
|
|
64
|
-
namespace_stackable(:finallies, block)
|
|
12
|
+
%w[before before_validation after_validation after finally].each do |callback_method|
|
|
13
|
+
define_method callback_method.to_sym do |&block|
|
|
14
|
+
inheritable_setting.namespace_stackable[callback_method.pluralize.to_sym] = block
|
|
65
15
|
end
|
|
66
16
|
end
|
|
67
17
|
end
|