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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -0
  3. data/CONTRIBUTING.md +2 -10
  4. data/README.md +106 -43
  5. data/UPGRADING.md +90 -1
  6. data/grape.gemspec +4 -4
  7. data/lib/grape/api/instance.rb +51 -73
  8. data/lib/grape/api.rb +56 -89
  9. data/lib/grape/cookies.rb +31 -25
  10. data/lib/grape/dry_types.rb +48 -4
  11. data/lib/grape/dsl/callbacks.rb +8 -58
  12. data/lib/grape/dsl/desc.rb +8 -67
  13. data/lib/grape/dsl/headers.rb +1 -1
  14. data/lib/grape/dsl/helpers.rb +60 -65
  15. data/lib/grape/dsl/inside_route.rb +26 -61
  16. data/lib/grape/dsl/logger.rb +3 -6
  17. data/lib/grape/dsl/middleware.rb +22 -40
  18. data/lib/grape/dsl/parameters.rb +10 -19
  19. data/lib/grape/dsl/request_response.rb +136 -139
  20. data/lib/grape/dsl/routing.rb +230 -194
  21. data/lib/grape/dsl/settings.rb +22 -134
  22. data/lib/grape/dsl/validations.rb +37 -45
  23. data/lib/grape/endpoint.rb +91 -126
  24. data/lib/grape/error_formatter/base.rb +2 -0
  25. data/lib/grape/exceptions/base.rb +1 -1
  26. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  27. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  28. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  29. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  30. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  31. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  32. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  33. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  34. data/lib/grape/extensions/hash.rb +2 -1
  35. data/lib/grape/extensions/hashie/mash.rb +3 -5
  36. data/lib/grape/locale/en.yml +44 -44
  37. data/lib/grape/middleware/auth/base.rb +11 -32
  38. data/lib/grape/middleware/auth/dsl.rb +22 -29
  39. data/lib/grape/middleware/base.rb +30 -11
  40. data/lib/grape/middleware/error.rb +14 -32
  41. data/lib/grape/middleware/formatter.rb +40 -72
  42. data/lib/grape/middleware/stack.rb +28 -38
  43. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -4
  44. data/lib/grape/middleware/versioner/base.rb +30 -56
  45. data/lib/grape/middleware/versioner/header.rb +2 -2
  46. data/lib/grape/middleware/versioner/param.rb +2 -3
  47. data/lib/grape/middleware/versioner/path.rb +1 -1
  48. data/lib/grape/namespace.rb +11 -0
  49. data/lib/grape/params_builder/base.rb +20 -0
  50. data/lib/grape/params_builder/hash.rb +11 -0
  51. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  52. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  53. data/lib/grape/params_builder.rb +32 -0
  54. data/lib/grape/request.rb +161 -22
  55. data/lib/grape/router/route.rb +1 -1
  56. data/lib/grape/router.rb +27 -8
  57. data/lib/grape/util/api_description.rb +56 -0
  58. data/lib/grape/util/base_inheritable.rb +5 -2
  59. data/lib/grape/util/inheritable_setting.rb +7 -0
  60. data/lib/grape/util/media_type.rb +1 -1
  61. data/lib/grape/util/registry.rb +1 -1
  62. data/lib/grape/validations/contract_scope.rb +2 -2
  63. data/lib/grape/validations/params_documentation.rb +50 -0
  64. data/lib/grape/validations/params_scope.rb +46 -56
  65. data/lib/grape/validations/types/array_coercer.rb +2 -3
  66. data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
  67. data/lib/grape/validations/types/primitive_coercer.rb +1 -28
  68. data/lib/grape/validations/types.rb +10 -25
  69. data/lib/grape/validations/validators/base.rb +2 -9
  70. data/lib/grape/validations/validators/except_values_validator.rb +1 -1
  71. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  72. data/lib/grape/validations/validators/regexp_validator.rb +1 -1
  73. data/lib/grape/version.rb +1 -1
  74. data/lib/grape.rb +18 -9
  75. metadata +35 -20
  76. data/lib/grape/api/helpers.rb +0 -9
  77. data/lib/grape/dsl/api.rb +0 -19
  78. data/lib/grape/dsl/configuration.rb +0 -15
  79. data/lib/grape/error_formatter/jsonapi.rb +0 -7
  80. data/lib/grape/http/headers.rb +0 -56
  81. data/lib/grape/middleware/helpers.rb +0 -12
  82. data/lib/grape/parser/jsonapi.rb +0 -7
  83. data/lib/grape/types/invalid_value.rb +0 -8
  84. data/lib/grape/util/lazy/object.rb +0 -45
  85. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  86. data/lib/grape/validations/attributes_doc.rb +0 -60
@@ -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
- include Grape::DSL::API
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
- evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block
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
- inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
75
- else
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(Grape::Http::Headers::X_CASCADE)
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
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
183
- return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options)&.key?(:cascade)
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 !self.class.namespace_inheritable(:do_not_route_head) && allowed_methods.include?(Rack::GET)
196
+ allowed_methods |= [Rack::HEAD] if !namespace_inheritable[:do_not_route_head] && allowed_methods.include?(Rack::GET)
216
197
 
217
- allow_header = self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Rack::OPTIONS] | allowed_methods
218
- last_route.app.options[:options_route_enabled] = true unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Rack::OPTIONS)
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
- old_version = self.class.namespace_inheritable(:version)
231
- old_version_options = self.class.namespace_inheritable(:version_options)
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
- self.class.namespace_inheritable(:version, old_version)
241
- self.class.namespace_inheritable(:version_options, old_version_options)
242
- self.class.namespace_inheritable(:root_prefix, old_root_prefix)
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
- # Rather than initializing an object of type Grape::API, create an object of type Instance
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
- api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
36
- api.override_all_methods!
37
- end
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, *args, &block)
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(opts = {})
86
- instance = Class.new(@base_parent)
87
- instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
88
- instance.base = self
89
- replay_setup_on(instance)
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
- def respond_to?(method, include_private = false)
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
- def method_missing(method, *args, &block)
110
- # If there's a missing method, it may be defined on the base_instance instead.
111
- if respond_to_missing?(method)
112
- base_instance.send(method, *args, &block)
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
- def compile!
119
- instance_for_rack.compile! # See API::Instance.compile!
85
+ api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
86
+ api.override_all_methods!
120
87
  end
121
88
 
122
- private
123
-
124
- def instance_for_rack
125
- if never_mounted?
126
- base_instance
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(method, *args, &block)
134
- setup_step = { method: method, args: args, block: block }
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, setup_step)
102
+ last_response = replay_step_on(instance, **step)
139
103
  end
140
104
 
141
- # Updating all previously mounted classes in the case that new methods have been executed.
142
- if method != :mount && @setup.any?
143
- previous_mount_steps = @setup.select { |step| step[:method] == :mount }
144
- previous_mount_steps.each do |mount_step|
145
- refresh_mount_step = mount_step.merge(method: :refresh_mounted_api)
146
- @setup += [refresh_mount_step]
147
- @instances.each do |instance|
148
- replay_step_on(instance, refresh_mount_step)
149
- end
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, setup_step)
157
- return if skip_immediate_run?(instance, setup_step[:args])
122
+ def replay_step_on(instance, method:, args:, kwargs:, block:)
123
+ return if skip_immediate_run?(instance, args, kwargs)
158
124
 
159
- args = evaluate_arguments(instance.configuration, *setup_step[:args])
160
- response = instance.send(setup_step[:method], *args, &setup_step[:block])
161
- if skip_immediate_run?(instance, [response])
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.respond_to?(:lazy?) && argument.lazy? }
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.respond_to?(:lazy?) && argument.lazy?
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
- def initialize
6
- @cookies = {}
7
- @send_cookies = {}
8
- end
5
+ extend Forwardable
9
6
 
10
- def read(request)
11
- request.cookies.each do |name, value|
12
- @cookies[name.to_s] = value
13
- end
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 write(header)
17
- @cookies.select { |key, _value| @send_cookies[key] == true }.each do |name, value|
18
- cookie_value = value.is_a?(Hash) ? value : { value: value }
19
- Rack::Utils.set_cookie_header! header, name, cookie_value
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
- @cookies[name.to_s]
28
+ def []=(name, value)
29
+ cookies[name] = value
30
+ send_cookies << name
25
31
  end
26
32
 
27
- def []=(name, value)
28
- @cookies[name.to_s] = value
29
- @send_cookies[name.to_s] = true
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
- def each(&block)
33
- @cookies.each(&block)
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
- # see https://github.com/rack/rack/blob/main/lib/rack/utils.rb#L338-L340
37
- # rubocop:disable Layout/SpaceBeforeBrackets
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
@@ -2,9 +2,53 @@
2
2
 
3
3
  module Grape
4
4
  module DryTypes
5
- # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
6
- # a container in this case. Check documentation for more information
7
- # https://dry-rb.org/gems/dry-types/1.2/getting-started/
8
- include Dry.Types()
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
@@ -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
- extend ActiveSupport::Concern
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
- include Grape::DSL::Configuration
22
-
23
- module ClassMethods
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