grape 4.0.1 → 4.1.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -1
  3. data/README.md +195 -95
  4. data/UPGRADING.md +364 -0
  5. data/grape.gemspec +1 -1
  6. data/lib/grape/api/instance.rb +57 -30
  7. data/lib/grape/api.rb +50 -20
  8. data/lib/grape/content_types.rb +26 -9
  9. data/lib/grape/cookies.rb +1 -1
  10. data/lib/grape/declared_params_handler.rb +35 -7
  11. data/lib/grape/dsl/declared.rb +5 -4
  12. data/lib/grape/dsl/desc.rb +1 -2
  13. data/lib/grape/dsl/entity.rb +35 -19
  14. data/lib/grape/dsl/headers.rb +3 -3
  15. data/lib/grape/dsl/helpers.rb +5 -8
  16. data/lib/grape/dsl/inside_route.rb +33 -27
  17. data/lib/grape/dsl/logger.rb +10 -4
  18. data/lib/grape/dsl/middleware.rb +4 -9
  19. data/lib/grape/dsl/mounting.rb +149 -0
  20. data/lib/grape/dsl/parameters.rb +78 -104
  21. data/lib/grape/dsl/request_response.rb +35 -22
  22. data/lib/grape/dsl/routing.rb +17 -191
  23. data/lib/grape/dsl/settings.rb +1 -1
  24. data/lib/grape/dsl/version_options.rb +2 -2
  25. data/lib/grape/dsl/versioning.rb +98 -0
  26. data/lib/grape/endpoint/state.rb +15 -0
  27. data/lib/grape/endpoint.rb +229 -137
  28. data/lib/grape/error_formatter/base.rb +28 -13
  29. data/lib/grape/error_formatter/json.rb +3 -0
  30. data/lib/grape/error_formatter/jsonl.rb +15 -0
  31. data/lib/grape/error_formatter/xml.rb +3 -1
  32. data/lib/grape/exceptions/error_response.rb +23 -7
  33. data/lib/grape/exceptions/halt.rb +39 -0
  34. data/lib/grape/exceptions/validation.rb +9 -9
  35. data/lib/grape/exceptions/validation_errors.rb +12 -2
  36. data/lib/grape/formatter/jsonl.rb +34 -0
  37. data/lib/grape/formatter/markdown.rb +17 -0
  38. data/lib/grape/formatter/md.rb +11 -0
  39. data/lib/grape/locale/en.yml +1 -0
  40. data/lib/grape/middleware/auth/base.rb +16 -1
  41. data/lib/grape/middleware/auth/strategy_info.rb +2 -2
  42. data/lib/grape/middleware/base.rb +19 -13
  43. data/lib/grape/middleware/error.rb +133 -35
  44. data/lib/grape/middleware/formatter.rb +235 -94
  45. data/lib/grape/middleware/precomputed_content_types.rb +14 -2
  46. data/lib/grape/middleware/stack.rb +7 -8
  47. data/lib/grape/middleware/versioner/base.rb +26 -10
  48. data/lib/grape/middleware/versioner/header.rb +70 -15
  49. data/lib/grape/middleware/versioner/param.rb +3 -5
  50. data/lib/grape/middleware/versioner/path.rb +20 -11
  51. data/lib/grape/middleware/versioner.rb +1 -1
  52. data/lib/grape/mountable.rb +2 -4
  53. data/lib/grape/namespace.rb +2 -3
  54. data/lib/grape/parser/jsonl.rb +19 -0
  55. data/lib/grape/request.rb +46 -8
  56. data/lib/grape/router/base_route.rb +41 -0
  57. data/lib/grape/router/greedy_route.rb +1 -1
  58. data/lib/grape/router/mustermann_pattern.rb +1 -1
  59. data/lib/grape/router/pattern/path.rb +1 -1
  60. data/lib/grape/router/pattern.rb +11 -5
  61. data/lib/grape/router/route.rb +42 -11
  62. data/lib/grape/router/route_buckets.rb +246 -0
  63. data/lib/grape/router.rb +262 -32
  64. data/lib/grape/util/endpoint_configuration.rb +7 -1
  65. data/lib/grape/util/inheritable_setting.rb +80 -161
  66. data/lib/grape/util/lazy/value.rb +52 -11
  67. data/lib/grape/util/media_type.rb +38 -15
  68. data/lib/grape/util/path_normalizer.rb +7 -2
  69. data/lib/grape/util/registry.rb +9 -2
  70. data/lib/grape/util/scrub.rb +33 -0
  71. data/lib/grape/util/translation.rb +6 -6
  72. data/lib/grape/validations/attributes_iterator.rb +73 -37
  73. data/lib/grape/validations/coerce_options.rb +1 -1
  74. data/lib/grape/validations/params_documentation.rb +15 -7
  75. data/lib/grape/validations/params_scope.rb +176 -43
  76. data/lib/grape/validations/types/custom_type_coercer.rb +2 -3
  77. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  78. data/lib/grape/validations/types/dry_type_coercer.rb +20 -12
  79. data/lib/grape/validations/types/multiple_type_coercer.rb +1 -2
  80. data/lib/grape/validations/types/primitive_coercer.rb +17 -0
  81. data/lib/grape/validations/types/variant_collection_coercer.rb +24 -10
  82. data/lib/grape/validations/types.rb +12 -18
  83. data/lib/grape/validations/validations_spec.rb +53 -22
  84. data/lib/grape/validations/validators/allow_blank_validator.rb +2 -2
  85. data/lib/grape/validations/validators/as_validator.rb +2 -3
  86. data/lib/grape/validations/validators/base.rb +124 -25
  87. data/lib/grape/validations/validators/coerce_validator.rb +2 -3
  88. data/lib/grape/validations/validators/default_validator.rb +18 -2
  89. data/lib/grape/validations/validators/except_values_validator.rb +16 -3
  90. data/lib/grape/validations/validators/hash_elements_validator.rb +34 -0
  91. data/lib/grape/validations/validators/length_validator.rb +24 -16
  92. data/lib/grape/validations/validators/multiple_params_base.rb +0 -4
  93. data/lib/grape/validations/validators/values_validator.rb +18 -24
  94. data/lib/grape/version.rb +1 -1
  95. data/lib/grape.rb +3 -0
  96. metadata +18 -11
  97. data/lib/grape/util/lazy/value_array.rb +0 -21
  98. data/lib/grape/util/lazy/value_enumerable.rb +0 -31
  99. data/lib/grape/util/lazy/value_hash.rb +0 -21
  100. data/lib/grape/validations/multiple_attributes_iterator.rb +0 -13
  101. data/lib/grape/validations/single_attribute_iterator.rb +0 -28
@@ -6,18 +6,13 @@ module Grape
6
6
  # and describe the parameters accepted by an endpoint, or all endpoints
7
7
  # within a namespace.
8
8
  module Parameters
9
- # Set the module used to build the request.params.
9
+ # Set the builder used to build the request.params.
10
10
  #
11
- # @param build_with the ParamBuilder module to use when building request.params
12
- # Available builders are:
13
- #
14
- # * Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder (default)
15
- # * Grape::Extensions::Hash::ParamBuilder
16
- # * Grape::Extensions::Hashie::Mash::ParamBuilder
11
+ # @param build_with [Symbol] +:hash_with_indifferent_access+ (default,
12
+ # see +Grape.config.param_builder+), +:hash+ or +:hashie_mash+
17
13
  #
18
14
  # @example
19
15
  #
20
- # require 'grape/extenstions/hashie_mash'
21
16
  # class API < Grape::API
22
17
  # desc "Get collection"
23
18
  # params do
@@ -72,80 +67,78 @@ module Grape
72
67
  alias use_scope use
73
68
  alias includes use
74
69
 
75
- # Require one or more parameters for the current endpoint.
70
+ # @!method requires(*attrs, using: nil, except: nil, as: nil, **opts, &block)
71
+ # Require one or more parameters for the current endpoint.
76
72
  #
77
- # @param attrs list of parameters names, or, if :using is
78
- # passed as an option, which keys to include (:all or :none) from
79
- # the :using hash. Passing the options as a trailing positional Hash
80
- # is deprecated; pass them as keyword arguments instead.
81
- # @option attrs :type [Class] the type to coerce this parameter to before
82
- # passing it to the endpoint. See {Grape::Validations::Types} for a list of
83
- # types that are supported automatically. Custom classes may be used
84
- # where they define a class-level `::parse` method, or in conjunction
85
- # with the `:coerce_with` parameter. `JSON` may be supplied to denote
86
- # `JSON`-formatted objects or arrays of objects. `Array[JSON]` accepts
87
- # the same values as `JSON` but will wrap single objects in an `Array`.
88
- # @option attrs :types [Array<Class>] may be supplied in place of +:type+
89
- # to declare an attribute that has multiple allowed types. See
90
- # {Validations::Types::MultipleTypeCoercer} for more details on coercion
91
- # and validation rules for variant-type parameters.
92
- # @option attrs :desc [String] description to document this parameter
93
- # @option attrs :default [Object] default value, if parameter is optional
94
- # @option attrs :values [Array] permissable values for this field. If any
95
- # other value is given, it will be handled as a validation error
96
- # @option attrs :using [Hash[Symbol => Hash]] a hash defining keys and
97
- # options, like that returned by {Grape::Entity#documentation}. The value
98
- # of each key is an options hash accepting the same parameters
99
- # @option attrs :except [Array[Symbol]] a list of keys to exclude from
100
- # the :using Hash. The meaning of this depends on if :all or :none was
101
- # passed; :all + :except will make the :except fields optional, whereas
102
- # :none + :except will make the :except fields required
103
- # @option attrs :coerce_with [#parse, #call] method to be used when coercing
104
- # the parameter to the type named by `attrs[:type]`. Any class or object
105
- # that defines `::parse` or `::call` may be used.
73
+ # @param attrs list of parameters names, or, if :using is
74
+ # passed as an option, which keys to include (:all or :none) from
75
+ # the :using hash. Passing the options as a trailing positional Hash
76
+ # is deprecated; pass them as keyword arguments instead.
77
+ # @option attrs :type [Class] the type to coerce this parameter to before
78
+ # passing it to the endpoint. See {Grape::Validations::Types} for a list of
79
+ # types that are supported automatically. Custom classes may be used
80
+ # where they define a class-level `::parse` method, or in conjunction
81
+ # with the `:coerce_with` parameter. `JSON` may be supplied to denote
82
+ # `JSON`-formatted objects or arrays of objects. `Array[JSON]` accepts
83
+ # the same values as `JSON` but will wrap single objects in an `Array`.
84
+ # @option attrs :types [Array<Class>] may be supplied in place of +:type+
85
+ # to declare an attribute that has multiple allowed types. See
86
+ # {Validations::Types::MultipleTypeCoercer} for more details on coercion
87
+ # and validation rules for variant-type parameters.
88
+ # @option attrs :desc [String] description to document this parameter
89
+ # @option attrs :default [Object] default value, if parameter is optional
90
+ # @option attrs :values [Array] permissible values for this field. If any
91
+ # other value is given, it will be handled as a validation error
92
+ # @option attrs :using [Hash[Symbol => Hash]] a hash defining keys and
93
+ # options, like that returned by {Grape::Entity#documentation}. The value
94
+ # of each key is an options hash accepting the same parameters
95
+ # @option attrs :except [Array[Symbol]] a list of keys to exclude from
96
+ # the :using Hash. The meaning of this depends on if :all or :none was
97
+ # passed; :all + :except will make the :except fields optional, whereas
98
+ # :none + :except will make the :except fields required
99
+ # @option attrs :coerce_with [#parse, #call] method to be used when coercing
100
+ # the parameter to the type named by `attrs[:type]`. Any class or object
101
+ # that defines `::parse` or `::call` may be used.
106
102
  #
107
- # @example
103
+ # @example
108
104
  #
109
- # params do
110
- # # Basic usage: require a parameter of a certain type
111
- # requires :user_id, type: Integer
105
+ # params do
106
+ # # Basic usage: require a parameter of a certain type
107
+ # requires :user_id, type: Integer
112
108
  #
113
- # # You don't need to specify type; String is default
114
- # requires :foo
109
+ # # Without a type, the value is not coerced
110
+ # requires :foo
115
111
  #
116
- # # Multiple params can be specified at once if they share
117
- # # the same options.
118
- # requires :x, :y, :z, type: Date
112
+ # # Multiple params can be specified at once if they share
113
+ # # the same options.
114
+ # requires :x, :y, :z, type: Date
119
115
  #
120
- # # Nested parameters can be handled as hashes. You must
121
- # # pass in a block, within which you can use any of the
122
- # # parameters DSL methods.
123
- # requires :user, type: Hash do
124
- # requires :name, type: String
116
+ # # Nested parameters can be handled as hashes. You must
117
+ # # pass in a block, within which you can use any of the
118
+ # # parameters DSL methods.
119
+ # requires :user, type: Hash do
120
+ # requires :name, type: String
121
+ # end
125
122
  # end
126
- # end
127
- def requires(*attrs, using: nil, except: nil, as: nil, **opts, &)
128
- return redispatch_legacy_options(:requires, attrs, { using:, except:, as: }.compact.merge(opts), &) if legacy_options?(attrs)
129
-
130
- declare(attrs, opts, required: true, using:, except:, as:, &)
131
- end
132
-
133
- # Allow, but don't require, one or more parameters for the current
134
- # endpoint.
135
- # @param (see #requires)
136
- # @option (see #requires)
137
- def optional(*attrs, using: nil, except: nil, as: nil, **opts, &)
138
- return redispatch_legacy_options(:optional, attrs, { using:, except:, as: }.compact.merge(opts), &) if legacy_options?(attrs)
139
-
140
- declare(attrs, opts, required: false, using:, except:, as:, &)
123
+ #
124
+ # @!method optional(*attrs, using: nil, except: nil, as: nil, **opts, &block)
125
+ # Allow, but don't require, one or more parameters for the current
126
+ # endpoint.
127
+ # @param (see Grape::DSL::Parameters#requires)
128
+ # @option (see Grape::DSL::Parameters#requires)
129
+ { requires: true, optional: false }.each do |method_name, required|
130
+ define_method(method_name) do |*attrs, using: nil, except: nil, as: nil, **opts, &block|
131
+ return redispatch_legacy_options(method_name, attrs, { using:, except:, as: }.compact.merge(opts), &block) if legacy_options?(attrs)
132
+
133
+ declare(attrs, opts, required:, using:, except:, as:, &block)
134
+ end
141
135
  end
142
136
 
143
137
  # Define common settings for one or more parameters
144
138
  # @param (see #requires)
145
139
  # @option (see #requires)
146
140
  def with(**opts, &)
147
- new_group_attrs = @group&.deep_merge(opts) || opts
148
- new_group_scope(new_group_attrs, &)
141
+ new_group_scope(with_group_options(opts), &)
149
142
  end
150
143
 
151
144
  %i[mutually_exclusive exactly_one_of at_least_one_of all_or_none_of].each do |validator|
@@ -156,9 +149,9 @@ module Grape
156
149
 
157
150
  # Define a block of validations which should be applied if and only if
158
151
  # the given parameter is present. The parameters are not nested.
159
- # @param attr [Symbol] the parameter which, if present, triggers the
160
- # validations
161
- # @raise Grape::Exceptions::UnknownParameter if `attr` has not been
152
+ # @param attrs [Array<Symbol, Hash>] the parameters which, if present (or
153
+ # passing the Proc they map to), trigger the validations
154
+ # @raise Grape::Exceptions::UnknownParameter if one has not been
162
155
  # defined in this scope yet
163
156
  # @yield a parameter definition DSL
164
157
  def given(*attrs, &)
@@ -176,8 +169,7 @@ module Grape
176
169
  # Elements of @declared_params of lateral scope are pushed in @parent. So check them in @parent.
177
170
  return @parent.declared_param?(param) if lateral?
178
171
 
179
- # @declared_params also includes hashes of options and such, but those
180
- # won't be flattened out.
172
+ # A nested scope is recorded as an Attr keyed by { element => children }.
181
173
  @declared_params.flatten.any? do |declared_param_attr|
182
174
  first_hash_key_or_param(declared_param_attr.key) == param
183
175
  end
@@ -185,22 +177,8 @@ module Grape
185
177
 
186
178
  alias group requires
187
179
 
188
- EmptyOptionalValue = Object.new.freeze
189
-
190
- # @param params [Hash] initial hash of parameters
191
- # @return hash of parameters relevant for the current scope
192
- # @api private
193
- def params(params)
194
- scoped = @parent ? (@parent.qualifying_params.presence || @parent.params(params)) : params
195
- @element ? map_params(scoped, @element) : scoped
196
- end
197
-
198
180
  private
199
181
 
200
- # @deprecated A trailing positional options Hash is deprecated; pass keyword
201
- # arguments instead. Before Ruby 3 keyword separation this Hash was pulled
202
- # off the argument list by `extract_options!`; now it lands in the splat and
203
- # would silently be treated as a parameter name.
204
182
  # +requires+ and +optional+ differ only in whether what they declare is
205
183
  # required, so the declaration itself lives here.
206
184
  #
@@ -209,7 +187,7 @@ module Grape
209
187
  # other bucket", while +optional+ has no other bucket — +:all+ ignores
210
188
  # +except+ and +:none+ uses it to drop fields entirely.
211
189
  def declare(attrs, opts, required:, using:, except:, as:, &block)
212
- merged_opts = @group&.deep_merge(opts) || opts
190
+ merged_opts = with_group_options(opts)
213
191
  declared_as = as || merged_opts[:as]
214
192
 
215
193
  if using
@@ -218,12 +196,23 @@ module Grape
218
196
  return require_optional_fields(attrs.first, using:, except:)
219
197
  end
220
198
 
199
+ # A block declares the keys of one param. Without a name its params
200
+ # landed one level up; with several, only the first got them.
201
+ raise ArgumentError, "a params block needs exactly one parameter name, got `#{attrs.size}`" if block && attrs.size != 1
202
+
221
203
  validates(attrs, merged_opts, required:)
222
204
  return push_declared_params(attrs, as: declared_as) unless block
223
205
 
224
206
  new_scope(attrs.first, type: merged_opts[:type], as: declared_as, optional: !required, &block)
225
207
  end
226
208
 
209
+ # +opts+ over the options of the +with+ blocks enclosing the declaration.
210
+ def with_group_options(opts)
211
+ @group&.deep_merge(opts) || opts
212
+ end
213
+
214
+ # A trailing positional options Hash (deprecated): since Ruby 3 keyword
215
+ # separation it lands in the splat and would be read as a parameter name.
227
216
  def legacy_options?(args)
228
217
  args.size > 1 && args.last.is_a?(Hash)
229
218
  end
@@ -239,21 +228,6 @@ module Grape
239
228
  def first_hash_key_or_param(parameter)
240
229
  parameter.is_a?(Hash) ? parameter.keys.first : parameter
241
230
  end
242
-
243
- def map_params(params, element, is_array: false)
244
- case params
245
- when Array
246
- params.map do |el|
247
- map_params(el, element, is_array: true)
248
- end
249
- when Hash
250
- params[element] || (@optional && is_array ? EmptyOptionalValue : {})
251
- when EmptyOptionalValue
252
- EmptyOptionalValue
253
- else
254
- {}
255
- end
256
- end
257
231
  end
258
232
  end
259
233
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module DSL
5
5
  module RequestResponse
6
6
  # Specify the default format for the API's serializers.
7
- # May be `:json` or `:txt` (default).
7
+ # e.g. `:json`, `:xml` or `:txt` (default).
8
8
  def default_format(new_format = nil)
9
9
  return inheritable_setting.default_format if new_format.nil?
10
10
 
@@ -13,16 +13,18 @@ module Grape
13
13
 
14
14
  # Specify the format for the API's serializers.
15
15
  # May be `:json`, `:xml`, `:txt`, etc.
16
+ # A format no content type is registered for used to be assigned before
17
+ # the check that rejects it, so a rescued call left the API formatting
18
+ # with a format it had just refused, and no default error formatter.
16
19
  def format(new_format = nil)
17
20
  return inheritable_setting.format if new_format.nil?
18
21
 
19
22
  symbolic_new_format = new_format.to_sym
20
- inheritable_setting.format = symbolic_new_format
21
- inheritable_setting.default_error_formatter = Grape::ErrorFormatter.formatter_for(symbolic_new_format)
22
-
23
23
  content_type = content_types[symbolic_new_format]
24
24
  raise Grape::Exceptions::MissingMimeType.new(new_format) unless content_type
25
25
 
26
+ inheritable_setting.format = symbolic_new_format
27
+ inheritable_setting.default_error_formatter = Grape::ErrorFormatter.formatter_for(symbolic_new_format)
26
28
  inheritable_setting.add_content_type(symbolic_new_format, content_type)
27
29
  end
28
30
 
@@ -78,6 +80,9 @@ module Grape
78
80
  inheritable_setting.default_error_status = new_status
79
81
  end
80
82
 
83
+ META_RESCUE_SELECTORS = %i[all grape_exceptions internal_grape_exceptions].freeze
84
+ private_constant :META_RESCUE_SELECTORS
85
+
81
86
  # Allows you to rescue certain exceptions that occur to return
82
87
  # a grape error rather than raising all the way to the
83
88
  # server level.
@@ -89,17 +94,15 @@ module Grape
89
94
  # rescue_from CustomError
90
95
  # end
91
96
  #
92
- META_RESCUE_SELECTORS = %i[all grape_exceptions internal_grape_exceptions].freeze
93
- private_constant :META_RESCUE_SELECTORS
94
-
95
97
  # @overload rescue_from(*exception_classes, **options)
96
98
  # @param [Array] exception_classes A list of classes that you want to rescue, or
97
99
  # one of the meta selectors +:all+, +:grape_exceptions+,
98
100
  # +:internal_grape_exceptions+. Meta selectors must be used alone;
99
101
  # mixing with exception classes raises +ArgumentError+.
100
- # @param [Block] block Execution block to handle the given exception.
101
- # @param [Proc] with Execution proc to handle the given exception as an alternative
102
- # to passing a block.
102
+ # @param [Block] block Execution block to handle the given exception; a
103
+ # trailing Proc argument works too.
104
+ # @param [Proc, Symbol, String] with Alternative to a block; a Symbol or
105
+ # String names an endpoint method.
103
106
  # @param [Boolean] rescue_subclasses Also rescue subclasses of exception classes;
104
107
  # defaults to +true+.
105
108
  # @param [Boolean] backtrace Include the rescued exception's backtrace in the
@@ -111,17 +114,7 @@ module Grape
111
114
  meta_selector = (args & META_RESCUE_SELECTORS).first
112
115
  raise ArgumentError, "rescue_from `#{meta_selector.inspect}` does not accept additional arguments" if meta_selector && args.size > 1
113
116
 
114
- case meta_selector
115
- when :all
116
- inheritable_setting.add_all_rescue_handler(handler)
117
- when :grape_exceptions
118
- inheritable_setting.add_grape_exceptions_rescue_handler(handler)
119
- when :internal_grape_exceptions
120
- inheritable_setting.add_internal_grape_exceptions_rescue_handler(handler)
121
- else
122
- inheritable_setting.add_rescue_handlers(args.to_h { |klass| [klass, handler] }, subclasses: rescue_subclasses)
123
- end
124
-
117
+ add_rescue_handler(meta_selector, args, handler, rescue_subclasses)
125
118
  inheritable_setting.add_rescue_options(RescueOptions.new(backtrace:, original_exception:))
126
119
  end
127
120
 
@@ -158,8 +151,28 @@ module Grape
158
151
 
159
152
  return args.pop if args.last.is_a?(Proc)
160
153
  return block if block
161
- return unless with
162
154
 
155
+ with_handler(with) if with
156
+ end
157
+
158
+ # Registers +handler+ under +meta_selector+, or for each of the
159
+ # +exception_classes+ when there is none.
160
+ def add_rescue_handler(meta_selector, exception_classes, handler, rescue_subclasses)
161
+ case meta_selector
162
+ when :all
163
+ inheritable_setting.add_all_rescue_handler(handler)
164
+ when :grape_exceptions
165
+ inheritable_setting.add_grape_exceptions_rescue_handler(handler)
166
+ when :internal_grape_exceptions
167
+ inheritable_setting.add_internal_grape_exceptions_rescue_handler(handler)
168
+ else
169
+ inheritable_setting.add_rescue_handlers(exception_classes.to_h { |klass| [klass, handler] }, subclasses: rescue_subclasses)
170
+ end
171
+ end
172
+
173
+ # A +with:+ handler as +rescue_from+ stores it: a Proc, or the name of a
174
+ # method to call.
175
+ def with_handler(with)
163
176
  case with
164
177
  when Proc, Symbol then with
165
178
  when String then with.to_sym
@@ -5,85 +5,6 @@ module Grape
5
5
  module Routing
6
6
  attr_reader :endpoints
7
7
 
8
- def given(conditional_option, &)
9
- return unless conditional_option
10
-
11
- mounted(&)
12
- end
13
-
14
- def mounted(&block)
15
- evaluate_as_instance_with_configuration(block, lazy: true)
16
- end
17
-
18
- def cascade(value = nil)
19
- return inheritable_setting.cascade_defined? ? inheritable_setting.cascade : true if value.nil?
20
-
21
- inheritable_setting.cascade = value
22
- end
23
-
24
- # Specify an API version.
25
- #
26
- # Called without arguments, returns the most recently declared version
27
- # (or +nil+). Called with one or more version strings, registers them
28
- # and stores a {Grape::DSL::VersionOptions} value object on the
29
- # inheritable settings; when given a block, the registration applies
30
- # within a nested namespace.
31
- #
32
- # @example API with legacy support.
33
- # class MyAPI < Grape::API
34
- # version 'v2'
35
- #
36
- # get '/main' do
37
- # {some: 'data'}
38
- # end
39
- #
40
- # version 'v1' do
41
- # get '/main' do
42
- # {legacy: 'data'}
43
- # end
44
- # end
45
- # end
46
- #
47
- # @param args [Array<String, Symbol>] one or more version identifiers.
48
- # @param using [Symbol] versioning strategy — one of +:path+ (default),
49
- # +:header+, +:param+, or +:accept_version_header+.
50
- # @param cascade [Boolean] forward to subsequent routes via the
51
- # +X-Cascade+ header on version mismatch. Defaults to +true+.
52
- # @param parameter [String] name of the query/body parameter that
53
- # carries the version when +using: :param+. Defaults to +'apiver'+.
54
- # @param strict [Boolean] reject requests that don't supply a usable
55
- # version (header strategies). Defaults to +false+.
56
- # @param vendor [String, nil] vendor segment for the +:header+
57
- # strategy (+application/vnd.<vendor>-<version>+); required when
58
- # +using: :header+.
59
- # @yield optional block to scope routes under this version.
60
- # @return [String, nil] the most recently declared version.
61
- # @raise [Grape::Exceptions::MissingVendorOption] when +using: :header+
62
- # is supplied without a +:vendor+.
63
- def version(*args, using: :path, cascade: true, parameter: 'apiver', strict: false, vendor: nil, &block)
64
- return @versions&.last if args.empty?
65
-
66
- raise Grape::Exceptions::MissingVendorOption.new if using == :header && vendor.nil?
67
-
68
- requested_versions = args.flatten.map(&:to_s)
69
- options = VersionOptions.new(using:, cascade:, parameter:, strict:, vendor:)
70
-
71
- @versions = versions | requested_versions
72
-
73
- if block
74
- within_namespace do
75
- inheritable_setting.version = requested_versions
76
- inheritable_setting.version_options = options
77
- instance_eval(&block)
78
- end
79
- else
80
- inheritable_setting.version = requested_versions
81
- inheritable_setting.version_options = options
82
- end
83
-
84
- @versions&.last
85
- end
86
-
87
8
  # Define a root URL prefix for your entire API.
88
9
  def prefix(prefix = nil)
89
10
  return inheritable_setting.root_prefix if prefix.nil?
@@ -96,9 +17,7 @@ module Grape
96
17
  # @param _name [Symbol] Purely placebo, just allows to name the scope to
97
18
  # make the code more readable.
98
19
  def scope(_name = nil, &block)
99
- within_namespace do
100
- nest(block)
101
- end
20
+ within_namespace { nest(block) }
102
21
  end
103
22
 
104
23
  def build_with(build_with)
@@ -123,48 +42,6 @@ module Grape
123
42
  inheritable_setting.do_not_document!
124
43
  end
125
44
 
126
- def mount(mounts, opts = {})
127
- mount_opts = opts
128
- if opts[:refresh_already_mounted]
129
- Grape.deprecator.warn('`refresh_already_mounted` is not a `mount` option and will be ignored in a future release.')
130
- drop_endpoints_mounted_for(mounts)
131
- # Dropped before the recursion below re-enters with the same options,
132
- # so a Grape API does not warn once per mount and once per instance.
133
- mount_opts = opts.except(:refresh_already_mounted)
134
- end
135
-
136
- normalize_mounts(mounts).each_pair do |app, path|
137
- if app.respond_to?(:mount_instance)
138
- mount({ app.mount_instance(configuration: mount_opts[:with] || {}) => path }, mount_opts)
139
- next
140
- end
141
- in_setting = inheritable_setting
142
-
143
- # Past the mount_instance branch above, a Grape app here is an already
144
- # instantiated Grape::API::Instance (vs. a bare Rack app).
145
- if app.is_a?(Grape::Mountable)
146
- mount_path = Grape::Util::PathNormalizer.call(path)
147
- app.top_level_setting.add_mount_path(mount_path)
148
-
149
- app.inherit_settings(inheritable_setting)
150
-
151
- in_setting = app.top_level_setting
152
-
153
- app.change!
154
- change!
155
- end
156
-
157
- endpoints << Grape::Endpoint.new(
158
- in_setting,
159
- http_methods: :any,
160
- path:,
161
- app:,
162
- anchor: false,
163
- api: self
164
- )
165
- end
166
- end
167
-
168
45
  # Defines a route that will be recognized
169
46
  # by the Grape API.
170
47
  #
@@ -189,19 +66,15 @@ module Grape
189
66
  # +params+, +requirements+ and +anchor+ each travel as their own endpoint
190
67
  # input; the route-options bag keeps the description's other keys
191
68
  # (+success+, +tags+, …) plus any custom options.
192
- params = prepare_params(endpoint_description[:params])
193
- all_route_options = endpoint_description.except(:params)
194
- all_route_options.deep_merge!(route_options) if route_options.present?
195
-
196
69
  new_endpoint = Grape::Endpoint.new(
197
70
  inheritable_setting,
198
71
  http_methods:,
199
72
  path: paths,
200
73
  api: self,
201
- params:,
74
+ params: prepare_params(endpoint_description[:params]),
202
75
  requirements:,
203
76
  anchor:,
204
- route_options: all_route_options,
77
+ route_options: prepare_route_options(endpoint_description, route_options),
205
78
  &
206
79
  )
207
80
  endpoints << new_endpoint unless endpoints.include?(new_endpoint)
@@ -228,14 +101,14 @@ module Grape
228
101
  # # defines the endpoint: GET /foo/bar
229
102
  # end
230
103
  # end
231
- def namespace(space = nil, requirements: nil, **options, &block)
104
+ def namespace(space = nil, requirements: nil, **, &block)
232
105
  return inheritable_setting.namespace_path unless space || block
233
106
 
234
107
  validate_requirements!(requirements)
235
108
 
236
109
  within_namespace do
237
110
  nest(block) do
238
- inheritable_setting.add_namespace(Grape::Namespace.new(space, requirements:, **options)) if space
111
+ inheritable_setting.add_namespace(Grape::Namespace.new(space, requirements:, **)) if space
239
112
  end
240
113
  end
241
114
  end
@@ -247,14 +120,15 @@ module Grape
247
120
 
248
121
  # An array of API routes.
249
122
  def routes
250
- @routes ||= endpoints.map(&:routes).flatten
123
+ @routes ||= endpoints.flat_map(&:routes)
251
124
  end
252
125
 
253
126
  # This method allows you to quickly define a parameter route segment
254
127
  # in your API.
255
128
  #
256
129
  # @param param [Symbol] The name of the parameter you wish to declare.
257
- # @option options [Regexp, Class, Symbol] The constraint the declared parameter must meet — a Regexp, or a capture type such as +Integer+.
130
+ # @param requirements [Regexp, Class, Symbol] The constraint the parameter must meet.
131
+ # @param type [Class] Declares the parameter with this type.
258
132
  def route_param(param, requirements: nil, type: nil, **, &)
259
133
  # The param is named here, so the constraint is its own: nest whatever
260
134
  # it is, not just a Regexp. A Hash would name the param twice, or key a
@@ -270,11 +144,6 @@ module Grape
270
144
  namespace(":#{param}", requirements: param_requirements, **, &)
271
145
  end
272
146
 
273
- # @return array of defined versions
274
- def versions
275
- @versions ||= []
276
- end
277
-
278
147
  private
279
148
 
280
149
  # Requirements are keyed by param name and merged across namespaces, so
@@ -295,6 +164,15 @@ module Grape
295
164
  endpoint_params.deep_merge(description_params)
296
165
  end
297
166
 
167
+ # The route's description (+desc+) less its +params+, with any custom
168
+ # +route_options+ deep-merged over it.
169
+ def prepare_route_options(description, route_options)
170
+ description_options = description.except(:params)
171
+ return description_options if route_options.empty?
172
+
173
+ description_options.deep_merge!(route_options)
174
+ end
175
+
298
176
  # Remove all defined routes.
299
177
  def reset_routes!
300
178
  endpoints.each(&:reset_routes!)
@@ -305,45 +183,6 @@ module Grape
305
183
  @endpoints = []
306
184
  end
307
185
 
308
- # Re-mount +mounts+, replacing any endpoint already mounted for the same
309
- # base API rather than adding a second one. Called by
310
- # {Grape::API.refresh_mount_step} when a class-level method runs after the
311
- # API was mounted.
312
- def refresh_mounted_api(mounts, opts = {})
313
- drop_endpoints_mounted_for(mounts)
314
- mount(mounts, opts)
315
- end
316
-
317
- # A mounted Grape API is stored as the throwaway instance +mount+ built
318
- # for it, never as the class that was written, so endpoints are matched on
319
- # the base API both of them share.
320
- def drop_endpoints_mounted_for(mounts)
321
- normalize_mounts(mounts).each_key do |app|
322
- endpoints.delete_if { |endpoint| same_mounted_app?(endpoint.mounted_app, app) }
323
- end
324
- end
325
-
326
- # A bare app mounts at the root. The test is +Hash+ rather than
327
- # +respond_to?(:each_pair)+ because a Struct or an OpenStruct answers that
328
- # too, and neither can express an app => path mapping — their keys are
329
- # member names. Reading one as a mapping would silently mount nonsense
330
- # instead of mounting the app itself.
331
- def normalize_mounts(mounts)
332
- mounts.is_a?(Hash) ? mounts : { mounts => '/' }
333
- end
334
-
335
- # Two mounts refer to the same app when they share the same base Grape
336
- # API. +mount+ turns every mounted Grape API into a throwaway
337
- # +mount_instance+ (a fresh +Class.new+ per mount), so object identity
338
- # never holds across mounts; comparing the base is the real signal.
339
- # Plain Rack apps have no base and are mounted as-is, so they fall back
340
- # to object identity.
341
- def same_mounted_app?(mounted, app)
342
- return mounted.base.equal?(app.base) if mounted.respond_to?(:base) && app.respond_to?(:base)
343
-
344
- mounted.equal?(app)
345
- end
346
-
347
186
  # Execute first the provided block, then each of the
348
187
  # block passed in. Allows for simple 'before' setups
349
188
  # of settings stack pushes.
@@ -355,19 +194,6 @@ module Grape
355
194
  blocks.each { |b| evaluate_as_instance_with_configuration(b) }
356
195
  reset_validations!
357
196
  end
358
-
359
- def evaluate_as_instance_with_configuration(block, lazy: false)
360
- lazy_block = Grape::Util::Lazy::Block.new do |configuration|
361
- value_for_configuration = configuration
362
- self.configuration = value_for_configuration.evaluate if value_for_configuration.is_a?(Grape::Util::Lazy::Base)
363
- response = instance_eval(&block)
364
- self.configuration = value_for_configuration
365
- response
366
- end
367
- return lazy_block if @base && base_instance? && lazy
368
-
369
- lazy_block.evaluate_from(configuration)
370
- end
371
197
  end
372
198
  end
373
199
  end
@@ -47,7 +47,7 @@ module Grape
47
47
  private
48
48
 
49
49
  # Execute the block within a context where our inheritable settings are forked
50
- # to a new copy (see #namespace_start).
50
+ # to a new child scope.
51
51
  def within_namespace
52
52
  new_inheritable_settings = Grape::Util::InheritableSetting.new
53
53
  new_inheritable_settings.inherit_from inheritable_setting