grape 2.4.0 → 3.2.1

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +109 -0
  3. data/CONTRIBUTING.md +2 -10
  4. data/README.md +137 -181
  5. data/UPGRADING.md +154 -0
  6. data/grape.gemspec +4 -4
  7. data/lib/grape/api/instance.rb +51 -104
  8. data/lib/grape/api.rb +29 -42
  9. data/lib/grape/content_types.rb +1 -4
  10. data/lib/grape/declared_params_handler.rb +118 -0
  11. data/lib/grape/dry_types.rb +48 -4
  12. data/lib/grape/dsl/callbacks.rb +8 -58
  13. data/lib/grape/dsl/declared.rb +35 -0
  14. data/lib/grape/dsl/desc.rb +8 -67
  15. data/lib/grape/dsl/helpers.rb +59 -64
  16. data/lib/grape/dsl/inside_route.rb +28 -189
  17. data/lib/grape/dsl/logger.rb +3 -6
  18. data/lib/grape/dsl/middleware.rb +22 -40
  19. data/lib/grape/dsl/parameters.rb +24 -51
  20. data/lib/grape/dsl/request_response.rb +136 -139
  21. data/lib/grape/dsl/routing.rb +240 -200
  22. data/lib/grape/dsl/settings.rb +23 -135
  23. data/lib/grape/dsl/validations.rb +38 -44
  24. data/lib/grape/endpoint.rb +169 -205
  25. data/lib/grape/error_formatter/base.rb +4 -2
  26. data/lib/grape/exceptions/base.rb +19 -45
  27. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  28. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  29. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  30. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  31. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  32. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  33. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  34. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  35. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  36. data/lib/grape/exceptions/request_error.rb +11 -0
  37. data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
  38. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  39. data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
  40. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  41. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  42. data/lib/grape/exceptions/validation.rb +7 -4
  43. data/lib/grape/exceptions/validation_errors.rb +13 -7
  44. data/lib/grape/locale/en.yml +0 -5
  45. data/lib/grape/middleware/auth/base.rb +2 -0
  46. data/lib/grape/middleware/auth/dsl.rb +9 -10
  47. data/lib/grape/middleware/base.rb +6 -4
  48. data/lib/grape/middleware/error.rb +4 -14
  49. data/lib/grape/middleware/formatter.rb +11 -7
  50. data/lib/grape/middleware/stack.rb +3 -2
  51. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
  52. data/lib/grape/middleware/versioner/base.rb +42 -40
  53. data/lib/grape/middleware/versioner/header.rb +2 -18
  54. data/lib/grape/middleware/versioner/param.rb +2 -2
  55. data/lib/grape/middleware/versioner/path.rb +2 -2
  56. data/lib/grape/namespace.rb +15 -8
  57. data/lib/grape/params_builder/base.rb +2 -0
  58. data/lib/grape/params_builder.rb +2 -19
  59. data/lib/grape/request.rb +2 -10
  60. data/lib/grape/router/base_route.rb +14 -5
  61. data/lib/grape/router/greedy_route.rb +11 -5
  62. data/lib/grape/router/pattern.rb +6 -20
  63. data/lib/grape/router/route.rb +7 -11
  64. data/lib/grape/router.rb +42 -65
  65. data/lib/grape/util/api_description.rb +58 -0
  66. data/lib/grape/util/base_inheritable.rb +5 -2
  67. data/lib/grape/util/cache.rb +1 -0
  68. data/lib/grape/util/deep_freeze.rb +35 -0
  69. data/lib/grape/util/inheritable_setting.rb +8 -1
  70. data/lib/grape/util/media_type.rb +2 -2
  71. data/lib/grape/util/registry.rb +1 -1
  72. data/lib/grape/util/translation.rb +42 -0
  73. data/lib/grape/validations/attributes_iterator.rb +35 -20
  74. data/lib/grape/validations/contract_scope.rb +2 -8
  75. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  76. data/lib/grape/validations/param_scope_tracker.rb +57 -0
  77. data/lib/grape/validations/params_documentation.rb +50 -0
  78. data/lib/grape/validations/params_scope.rb +147 -156
  79. data/lib/grape/validations/single_attribute_iterator.rb +2 -2
  80. data/lib/grape/validations/types/array_coercer.rb +2 -3
  81. data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
  82. data/lib/grape/validations/types/primitive_coercer.rb +1 -28
  83. data/lib/grape/validations/types.rb +10 -25
  84. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  85. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  86. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  87. data/lib/grape/validations/validators/base.rb +95 -25
  88. data/lib/grape/validations/validators/coerce_validator.rb +15 -35
  89. data/lib/grape/validations/validators/contract_scope_validator.rb +9 -5
  90. data/lib/grape/validations/validators/default_validator.rb +12 -18
  91. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  92. data/lib/grape/validations/validators/except_values_validator.rb +13 -4
  93. data/lib/grape/validations/validators/length_validator.rb +21 -22
  94. data/lib/grape/validations/validators/multiple_params_base.rb +5 -5
  95. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  96. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  97. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  98. data/lib/grape/validations/validators/same_as_validator.rb +6 -15
  99. data/lib/grape/validations/validators/values_validator.rb +29 -21
  100. data/lib/grape/version.rb +1 -1
  101. data/lib/grape.rb +29 -23
  102. metadata +31 -26
  103. data/lib/grape/api/helpers.rb +0 -9
  104. data/lib/grape/dsl/api.rb +0 -17
  105. data/lib/grape/dsl/configuration.rb +0 -15
  106. data/lib/grape/exceptions/conflicting_types.rb +0 -11
  107. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  108. data/lib/grape/exceptions/invalid_parameters.rb +0 -11
  109. data/lib/grape/exceptions/missing_option.rb +0 -11
  110. data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
  111. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  112. data/lib/grape/exceptions/unknown_options.rb +0 -11
  113. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
  114. data/lib/grape/extensions/hash.rb +0 -27
  115. data/lib/grape/extensions/hashie/mash.rb +0 -24
  116. data/lib/grape/types/invalid_value.rb +0 -8
  117. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  118. data/lib/grape/validations/attributes_doc.rb +0 -60
  119. data/lib/grape/validations/validator_factory.rb +0 -15
@@ -3,244 +3,284 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Routing
6
- extend ActiveSupport::Concern
7
- include Grape::DSL::Configuration
8
-
9
- module ClassMethods
10
- attr_reader :endpoints
11
-
12
- # Specify an API version.
13
- #
14
- # @example API with legacy support.
15
- # class MyAPI < Grape::API
16
- # version 'v2'
17
- #
18
- # get '/main' do
19
- # {some: 'data'}
20
- # end
21
- #
22
- # version 'v1' do
23
- # get '/main' do
24
- # {legacy: 'data'}
25
- # end
26
- # end
27
- # end
28
- #
29
- def version(*args, &block)
30
- if args.any?
31
- options = args.extract_options!
32
- options = options.reverse_merge(using: :path)
33
- requested_versions = args.flatten.map(&:to_s)
34
-
35
- raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
36
-
37
- @versions = versions | requested_versions
38
-
39
- if block
40
- within_namespace do
41
- namespace_inheritable(:version, requested_versions)
42
- namespace_inheritable(:version_options, options)
43
-
44
- instance_eval(&block)
45
- end
46
- else
47
- namespace_inheritable(:version, requested_versions)
48
- namespace_inheritable(:version_options, options)
49
- end
50
- end
6
+ attr_reader :endpoints
51
7
 
52
- @versions.last if instance_variable_defined?(:@versions) && @versions
53
- end
8
+ def given(conditional_option, &)
9
+ return unless conditional_option
54
10
 
55
- # Define a root URL prefix for your entire API.
56
- def prefix(prefix = nil)
57
- namespace_inheritable(:root_prefix, prefix&.to_s)
58
- end
11
+ mounted(&)
12
+ end
13
+
14
+ def mounted(&block)
15
+ evaluate_as_instance_with_configuration(block, lazy: true)
16
+ end
59
17
 
60
- # Create a scope without affecting the URL.
61
- #
62
- # @param _name [Symbol] Purely placebo, just allows to name the scope to
63
- # make the code more readable.
64
- def scope(_name = nil, &block)
65
- within_namespace do
66
- nest(block)
18
+ def cascade(value = nil)
19
+ return inheritable_setting.namespace_inheritable.key?(:cascade) ? !inheritable_setting.namespace_inheritable[:cascade].nil? : true if value.nil?
20
+
21
+ inheritable_setting.namespace_inheritable[:cascade] = value
22
+ end
23
+
24
+ # Specify an API version.
25
+ #
26
+ # @example API with legacy support.
27
+ # class MyAPI < Grape::API
28
+ # version 'v2'
29
+ #
30
+ # get '/main' do
31
+ # {some: 'data'}
32
+ # end
33
+ #
34
+ # version 'v1' do
35
+ # get '/main' do
36
+ # {legacy: 'data'}
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ def version(*args, **options, &block)
42
+ if args.any?
43
+ options = options.reverse_merge(using: :path)
44
+ requested_versions = args.flatten.map(&:to_s)
45
+
46
+ raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
47
+
48
+ @versions = versions | requested_versions
49
+
50
+ if block
51
+ within_namespace do
52
+ inheritable_setting.namespace_inheritable[:version] = requested_versions
53
+ inheritable_setting.namespace_inheritable[:version_options] = options
54
+
55
+ instance_eval(&block)
56
+ end
57
+ else
58
+ inheritable_setting.namespace_inheritable[:version] = requested_versions
59
+ inheritable_setting.namespace_inheritable[:version_options] = options
67
60
  end
68
61
  end
69
62
 
70
- def build_with(build_with)
71
- namespace_inheritable(:build_params_with, build_with)
72
- end
63
+ @versions&.last
64
+ end
73
65
 
74
- # Do not route HEAD requests to GET requests automatically.
75
- def do_not_route_head!
76
- namespace_inheritable(:do_not_route_head, true)
77
- end
66
+ # Define a root URL prefix for your entire API.
67
+ def prefix(prefix = nil)
68
+ return inheritable_setting.namespace_inheritable[:root_prefix] if prefix.nil?
78
69
 
79
- # Do not automatically route OPTIONS.
80
- def do_not_route_options!
81
- namespace_inheritable(:do_not_route_options, true)
82
- end
70
+ inheritable_setting.namespace_inheritable[:root_prefix] = prefix.to_s
71
+ end
83
72
 
84
- def lint!
85
- namespace_inheritable(:lint, true)
73
+ # Create a scope without affecting the URL.
74
+ #
75
+ # @param _name [Symbol] Purely placebo, just allows to name the scope to
76
+ # make the code more readable.
77
+ def scope(_name = nil, &block)
78
+ within_namespace do
79
+ nest(block)
86
80
  end
81
+ end
87
82
 
88
- def do_not_document!
89
- namespace_inheritable(:do_not_document, true)
90
- end
83
+ def build_with(build_with)
84
+ inheritable_setting.namespace_inheritable[:build_params_with] = build_with
85
+ end
91
86
 
92
- def mount(mounts, *opts)
93
- mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
94
- mounts.each_pair do |app, path|
95
- if app.respond_to?(:mount_instance)
96
- opts_with = opts.any? ? opts.first[:with] : {}
97
- mount({ app.mount_instance(configuration: opts_with) => path }, *opts)
98
- next
99
- end
100
- in_setting = inheritable_setting
87
+ # Do not route HEAD requests to GET requests automatically.
88
+ def do_not_route_head!
89
+ inheritable_setting.namespace_inheritable[:do_not_route_head] = true
90
+ end
101
91
 
102
- if app.respond_to?(:inheritable_setting, true)
103
- mount_path = Grape::Router.normalize_path(path)
104
- app.top_level_setting.namespace_stackable[:mount_path] = mount_path
92
+ # Do not automatically route OPTIONS.
93
+ def do_not_route_options!
94
+ inheritable_setting.namespace_inheritable[:do_not_route_options] = true
95
+ end
105
96
 
106
- app.inherit_settings(inheritable_setting)
97
+ def lint!
98
+ inheritable_setting.namespace_inheritable[:lint] = true
99
+ end
107
100
 
108
- in_setting = app.top_level_setting
101
+ def do_not_document!
102
+ inheritable_setting.namespace_inheritable[:do_not_document] = true
103
+ end
109
104
 
110
- app.change!
111
- change!
112
- end
105
+ def mount(mounts, *opts)
106
+ mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
107
+ mounts.each_pair do |app, path|
108
+ if app.respond_to?(:mount_instance)
109
+ opts_with = opts.any? ? opts.first[:with] : {}
110
+ mount({ app.mount_instance(configuration: opts_with) => path }, *opts)
111
+ next
112
+ end
113
+ in_setting = inheritable_setting
113
114
 
114
- # When trying to mount multiple times the same endpoint, remove the previous ones
115
- # from the list of endpoints if refresh_already_mounted parameter is true
116
- refresh_already_mounted = opts.any? ? opts.first[:refresh_already_mounted] : false
117
- if refresh_already_mounted && !endpoints.empty?
118
- endpoints.delete_if do |endpoint|
119
- endpoint.options[:app].to_s == app.to_s
120
- end
121
- end
115
+ if app.respond_to?(:inheritable_setting, true)
116
+ mount_path = Grape::Router.normalize_path(path)
117
+ app.top_level_setting.namespace_stackable[:mount_path] = mount_path
122
118
 
123
- endpoints << Grape::Endpoint.new(
124
- in_setting,
125
- method: :any,
126
- path: path,
127
- app: app,
128
- route_options: { anchor: false },
129
- forward_match: !app.respond_to?(:inheritable_setting),
130
- for: self
131
- )
132
- end
133
- end
119
+ app.inherit_settings(inheritable_setting)
134
120
 
135
- # Defines a route that will be recognized
136
- # by the Grape API.
137
- #
138
- # @param methods [HTTP Verb] One or more HTTP verbs that are accepted by this route. Set to `:any` if you want any verb to be accepted.
139
- # @param paths [String] One or more strings representing the URL segment(s) for this route.
140
- #
141
- # @example Defining a basic route.
142
- # class MyAPI < Grape::API
143
- # route(:any, '/hello') do
144
- # {hello: 'world'}
145
- # end
146
- # end
147
- def route(methods, paths = ['/'], route_options = {}, &block)
148
- methods = '*' if methods == :any
149
- endpoint_options = {
150
- method: methods,
151
- path: paths,
152
- for: self,
153
- route_options: {
154
- params: namespace_stackable_with_hash(:params) || {}
155
- }.deep_merge(route_setting(:description) || {}).deep_merge(route_options || {})
156
- }
157
-
158
- new_endpoint = Grape::Endpoint.new(inheritable_setting, endpoint_options, &block)
159
- endpoints << new_endpoint unless endpoints.any? { |e| e.equals?(new_endpoint) }
160
-
161
- route_end
162
- reset_validations!
163
- end
121
+ in_setting = app.top_level_setting
164
122
 
165
- Grape::HTTP_SUPPORTED_METHODS.each do |supported_method|
166
- define_method supported_method.downcase do |*args, &block|
167
- options = args.extract_options!
168
- paths = args.first || ['/']
169
- route(supported_method, paths, options, &block)
123
+ app.change!
124
+ change!
170
125
  end
171
- end
172
126
 
173
- # Declare a "namespace", which prefixes all subordinate routes with its
174
- # name. Any endpoints within a namespace, group, resource or segment,
175
- # etc., will share their parent context as well as any configuration
176
- # done in the namespace context.
177
- #
178
- # @example
179
- #
180
- # namespace :foo do
181
- # get 'bar' do
182
- # # defines the endpoint: GET /foo/bar
183
- # end
184
- # end
185
- def namespace(space = nil, options = {}, &block)
186
- return Namespace.joined_space_path(namespace_stackable(:namespace)) unless space || block
187
-
188
- within_namespace do
189
- nest(block) do
190
- namespace_stackable(:namespace, Namespace.new(space, options)) if space
127
+ # When trying to mount multiple times the same endpoint, remove the previous ones
128
+ # from the list of endpoints if refresh_already_mounted parameter is true
129
+ refresh_already_mounted = opts.any? ? opts.first[:refresh_already_mounted] : false
130
+ if refresh_already_mounted && !endpoints.empty?
131
+ endpoints.delete_if do |endpoint|
132
+ endpoint.options[:app].to_s == app.to_s
191
133
  end
192
134
  end
135
+
136
+ endpoints << Grape::Endpoint.new(
137
+ in_setting,
138
+ method: :any,
139
+ path:,
140
+ app:,
141
+ route_options: { anchor: false },
142
+ forward_match: !app.respond_to?(:inheritable_setting),
143
+ for: self
144
+ )
193
145
  end
146
+ end
194
147
 
195
- alias group namespace
196
- alias resource namespace
197
- alias resources namespace
198
- alias segment namespace
148
+ # Defines a route that will be recognized
149
+ # by the Grape API.
150
+ #
151
+ # @param methods [HTTP Verb] One or more HTTP verbs that are accepted by this route. Set to `:any` if you want any verb to be accepted.
152
+ # @param paths [String] One or more strings representing the URL segment(s) for this route.
153
+ #
154
+ # @example Defining a basic route.
155
+ # class MyAPI < Grape::API
156
+ # route(:any, '/hello') do
157
+ # {hello: 'world'}
158
+ # end
159
+ # end
160
+ def route(methods, paths = ['/'], route_options = {}, &)
161
+ method = methods == :any ? '*' : methods
162
+ endpoint_params = inheritable_setting.namespace_stackable_with_hash(:params) || {}
163
+ endpoint_description = inheritable_setting.route[:description]
164
+ all_route_options = { params: endpoint_params }
165
+ all_route_options.deep_merge!(endpoint_description) if endpoint_description
166
+ all_route_options.deep_merge!(route_options) if route_options&.any?
167
+
168
+ new_endpoint = Grape::Endpoint.new(
169
+ inheritable_setting,
170
+ method:,
171
+ path: paths,
172
+ for: self,
173
+ route_options: all_route_options,
174
+ &
175
+ )
176
+ endpoints << new_endpoint unless endpoints.include?(new_endpoint)
177
+
178
+ inheritable_setting.route_end
179
+ reset_validations!
180
+ end
199
181
 
200
- # An array of API routes.
201
- def routes
202
- @routes ||= prepare_routes
182
+ Grape::HTTP_SUPPORTED_METHODS.each do |supported_method|
183
+ define_method supported_method.downcase do |*args, **options, &block|
184
+ paths = args.first || ['/']
185
+ route(supported_method, paths, options, &block)
203
186
  end
187
+ end
204
188
 
205
- # Remove all defined routes.
206
- def reset_routes!
207
- endpoints.each(&:reset_routes!)
208
- @routes = nil
189
+ # Declare a "namespace", which prefixes all subordinate routes with its
190
+ # name. Any endpoints within a namespace, group, resource or segment,
191
+ # etc., will share their parent context as well as any configuration
192
+ # done in the namespace context.
193
+ #
194
+ # @example
195
+ #
196
+ # namespace :foo do
197
+ # get 'bar' do
198
+ # # defines the endpoint: GET /foo/bar
199
+ # end
200
+ # end
201
+ def namespace(space = nil, requirements: nil, **options, &block)
202
+ return Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace]) unless space || block
203
+
204
+ within_namespace do
205
+ nest(block) do
206
+ inheritable_setting.namespace_stackable[:namespace] = Grape::Namespace.new(space, requirements:, **options) if space
207
+ end
209
208
  end
209
+ end
210
210
 
211
- def reset_endpoints!
212
- @endpoints = []
213
- end
211
+ alias group namespace
212
+ alias resource namespace
213
+ alias resources namespace
214
+ alias segment namespace
214
215
 
215
- # This method allows you to quickly define a parameter route segment
216
- # in your API.
217
- #
218
- # @param param [Symbol] The name of the parameter you wish to declare.
219
- # @option options [Regexp] You may supply a regular expression that the declared parameter must meet.
220
- def route_param(param, options = {}, &block)
221
- options = options.dup
216
+ # An array of API routes.
217
+ def routes
218
+ @routes ||= endpoints.map(&:routes).flatten
219
+ end
222
220
 
223
- options[:requirements] = {
224
- param.to_sym => options[:requirements]
225
- } if options[:requirements].is_a?(Regexp)
221
+ # This method allows you to quickly define a parameter route segment
222
+ # in your API.
223
+ #
224
+ # @param param [Symbol] The name of the parameter you wish to declare.
225
+ # @option options [Regexp] You may supply a regular expression that the declared parameter must meet.
226
+ def route_param(param, requirements: nil, type: nil, **, &)
227
+ requirements = { param.to_sym => requirements } if requirements.is_a?(Regexp)
226
228
 
227
- Grape::Validations::ParamsScope.new(api: self) do
228
- requires param, type: options[:type]
229
- end if options.key?(:type)
229
+ Grape::Validations::ParamsScope.new(api: self) do
230
+ requires param, type: type
231
+ end if type
230
232
 
231
- namespace(":#{param}", options, &block)
232
- end
233
+ namespace(":#{param}", requirements:, **, &)
234
+ end
233
235
 
234
- # @return array of defined versions
235
- def versions
236
- @versions ||= []
237
- end
236
+ # @return array of defined versions
237
+ def versions
238
+ @versions ||= []
239
+ end
238
240
 
239
- private
241
+ private
242
+
243
+ # Remove all defined routes.
244
+ def reset_routes!
245
+ endpoints.each(&:reset_routes!)
246
+ @routes = nil
247
+ end
240
248
 
241
- def refresh_mounted_api(mounts, *opts)
242
- opts << { refresh_already_mounted: true }
243
- mount(mounts, *opts)
249
+ def reset_endpoints!
250
+ @endpoints = []
251
+ end
252
+
253
+ def refresh_mounted_api(mounts, *opts)
254
+ opts << { refresh_already_mounted: true }
255
+ mount(mounts, *opts)
256
+ end
257
+
258
+ # Execute first the provided block, then each of the
259
+ # block passed in. Allows for simple 'before' setups
260
+ # of settings stack pushes.
261
+ def nest(*blocks, &block)
262
+ blocks.compact!
263
+ if blocks.any?
264
+ evaluate_as_instance_with_configuration(block) if block
265
+ blocks.each { |b| evaluate_as_instance_with_configuration(b) }
266
+ reset_validations!
267
+ else
268
+ instance_eval(&block)
269
+ end
270
+ end
271
+
272
+ def evaluate_as_instance_with_configuration(block, lazy: false)
273
+ lazy_block = Grape::Util::Lazy::Block.new do |configuration|
274
+ value_for_configuration = configuration
275
+ self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
276
+ response = instance_eval(&block)
277
+ self.configuration = value_for_configuration
278
+ response
279
+ end
280
+ if @base && base_instance? && lazy
281
+ lazy_block
282
+ else
283
+ lazy_block.evaluate_from(configuration)
244
284
  end
245
285
  end
246
286
  end
@@ -7,13 +7,17 @@ module Grape
7
7
  # matter where they're defined, and inheritable settings which apply only
8
8
  # in the current scope and scopes nested under it.
9
9
  module Settings
10
- extend ActiveSupport::Concern
11
-
12
- attr_writer :inheritable_setting, :top_level_setting
10
+ attr_writer :inheritable_setting
13
11
 
14
12
  # Fetch our top-level settings, which apply to all endpoints in the API.
15
13
  def top_level_setting
16
- @top_level_setting ||= build_top_level_setting
14
+ @top_level_setting ||= Grape::Util::InheritableSetting.new.tap do |setting|
15
+ # Doesn't try to inherit settings from +Grape::API::Instance+ which also responds to
16
+ # +inheritable_setting+, however, it doesn't contain any user-defined settings.
17
+ # Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+
18
+ # in the chain for every endpoint.
19
+ setting.inherit_from superclass.inheritable_setting if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance
20
+ end
17
21
  end
18
22
 
19
23
  # Fetch our current inheritable settings, which are inherited by
@@ -22,157 +26,41 @@ module Grape
22
26
  @inheritable_setting ||= Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from top_level_setting }
23
27
  end
24
28
 
25
- # @param type [Symbol]
26
- # @param key [Symbol]
27
- def unset(type, key)
28
- setting = inheritable_setting.send(type)
29
- setting.delete key
30
- end
31
-
32
- # @param type [Symbol]
33
- # @param key [Symbol]
34
- # @param value [Object] will be stored if the value is currently empty
35
- # @return either the old value, if it wasn't nil, or the given value
36
- def get_or_set(type, key, value)
37
- setting = inheritable_setting.send(type)
38
- if value.nil?
39
- setting[key]
40
- else
41
- setting[key] = value
42
- end
43
- end
44
-
45
- # @param key [Symbol]
46
- # @param value [Object]
47
- # @return (see #get_or_set)
48
29
  def global_setting(key, value = nil)
49
- get_or_set :global, key, value
30
+ get_or_set(inheritable_setting.global, key, value)
50
31
  end
51
32
 
52
- # @param key [Symbol]
53
- def unset_global_setting(key)
54
- unset :global, key
55
- end
56
-
57
- # (see #global_setting)
58
33
  def route_setting(key, value = nil)
59
- get_or_set :route, key, value
60
- end
61
-
62
- # (see #unset_global_setting)
63
- def unset_route_setting(key)
64
- unset :route, key
34
+ get_or_set(inheritable_setting.route, key, value)
65
35
  end
66
36
 
67
- # (see #global_setting)
68
37
  def namespace_setting(key, value = nil)
69
- get_or_set :namespace, key, value
70
- end
71
-
72
- # (see #unset_global_setting)
73
- def unset_namespace_setting(key)
74
- unset :namespace, key
75
- end
76
-
77
- # (see #global_setting)
78
- def namespace_inheritable(key, value = nil)
79
- get_or_set :namespace_inheritable, key, value
80
- end
81
-
82
- # (see #unset_global_setting)
83
- def unset_namespace_inheritable(key)
84
- unset :namespace_inheritable, key
38
+ get_or_set(inheritable_setting.namespace, key, value)
85
39
  end
86
40
 
87
- # @param key [Symbol]
88
- def namespace_inheritable_to_nil(key)
89
- inheritable_setting.namespace_inheritable[key] = nil
90
- end
91
-
92
- # (see #global_setting)
93
- def namespace_stackable(key, value = nil)
94
- get_or_set :namespace_stackable, key, value
95
- end
96
-
97
- def namespace_reverse_stackable(key, value = nil)
98
- get_or_set :namespace_reverse_stackable, key, value
99
- end
100
-
101
- def namespace_stackable_with_hash(key)
102
- settings = get_or_set :namespace_stackable, key, nil
103
- return if settings.blank?
104
-
105
- settings.each_with_object({}) { |value, result| result.deep_merge!(value) }
106
- end
107
-
108
- def namespace_reverse_stackable_with_hash(key)
109
- settings = get_or_set :namespace_reverse_stackable, key, nil
110
- return if settings.blank?
111
-
112
- settings.each_with_object({}) do |setting, result|
113
- result.merge!(setting) { |_k, s1, _s2| s1 }
114
- end
115
- end
116
-
117
- # (see #unset_global_setting)
118
- def unset_namespace_stackable(key)
119
- unset :namespace_stackable, key
120
- end
121
-
122
- # (see #global_setting)
123
- def api_class_setting(key, value = nil)
124
- get_or_set :api_class, key, value
125
- end
126
-
127
- # (see #unset_global_setting)
128
- def unset_api_class_setting(key)
129
- unset :api_class, key
130
- end
131
-
132
- # Fork our inheritable settings to a new instance, copied from our
133
- # parent's, but separate so we won't modify it. Every call to this
134
- # method should have an answering call to #namespace_end.
135
- def namespace_start
136
- @inheritable_setting = Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from inheritable_setting }
137
- end
138
-
139
- # Set the inheritable settings pointer back up by one level.
140
- def namespace_end
141
- route_end
142
- @inheritable_setting = inheritable_setting.parent
143
- end
144
-
145
- # Stop defining settings for the current route and clear them for the
146
- # next, within a namespace.
147
- def route_end
148
- inheritable_setting.route_end
149
- end
41
+ private
150
42
 
151
43
  # Execute the block within a context where our inheritable settings are forked
152
44
  # to a new copy (see #namespace_start).
153
- def within_namespace(&block)
154
- namespace_start
45
+ def within_namespace
46
+ new_inheritable_settings = Grape::Util::InheritableSetting.new
47
+ new_inheritable_settings.inherit_from inheritable_setting
155
48
 
156
- result = yield if block
49
+ @inheritable_setting = new_inheritable_settings
157
50
 
158
- namespace_end
51
+ result = yield
52
+
53
+ inheritable_setting.route_end
54
+ @inheritable_setting = inheritable_setting.parent
159
55
  reset_validations!
160
56
 
161
57
  result
162
58
  end
163
59
 
164
- private
60
+ def get_or_set(setting, key, value)
61
+ return setting[key] if value.nil?
165
62
 
166
- # Builds the current class :inheritable_setting. If available, it inherits from
167
- # the superclass's :inheritable_setting.
168
- def build_top_level_setting
169
- Grape::Util::InheritableSetting.new.tap do |setting|
170
- # Doesn't try to inherit settings from +Grape::API::Instance+ which also responds to
171
- # +inheritable_setting+, however, it doesn't contain any user-defined settings.
172
- # Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+
173
- # in the chain for every endpoint.
174
- setting.inherit_from superclass.inheritable_setting if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance
175
- end
63
+ setting[key] = value
176
64
  end
177
65
  end
178
66
  end