grape 4.0.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +108 -1
- data/README.md +195 -95
- data/UPGRADING.md +364 -0
- data/grape.gemspec +1 -1
- data/lib/grape/api/instance.rb +57 -30
- data/lib/grape/api.rb +52 -18
- data/lib/grape/content_types.rb +26 -9
- data/lib/grape/cookies.rb +1 -1
- data/lib/grape/declared_params_handler.rb +35 -7
- data/lib/grape/dsl/declared.rb +5 -4
- data/lib/grape/dsl/desc.rb +1 -2
- data/lib/grape/dsl/entity.rb +35 -19
- data/lib/grape/dsl/headers.rb +3 -3
- data/lib/grape/dsl/helpers.rb +5 -8
- data/lib/grape/dsl/inside_route.rb +33 -27
- data/lib/grape/dsl/logger.rb +10 -4
- data/lib/grape/dsl/middleware.rb +4 -9
- data/lib/grape/dsl/mounting.rb +149 -0
- data/lib/grape/dsl/parameters.rb +78 -104
- data/lib/grape/dsl/request_response.rb +35 -22
- data/lib/grape/dsl/routing.rb +17 -191
- data/lib/grape/dsl/settings.rb +1 -1
- data/lib/grape/dsl/version_options.rb +2 -2
- data/lib/grape/dsl/versioning.rb +98 -0
- data/lib/grape/endpoint/state.rb +15 -0
- data/lib/grape/endpoint.rb +229 -137
- data/lib/grape/error_formatter/base.rb +28 -13
- data/lib/grape/error_formatter/json.rb +3 -0
- data/lib/grape/error_formatter/jsonl.rb +15 -0
- data/lib/grape/error_formatter/xml.rb +3 -1
- data/lib/grape/exceptions/error_response.rb +23 -7
- data/lib/grape/exceptions/halt.rb +39 -0
- data/lib/grape/exceptions/validation.rb +9 -9
- data/lib/grape/exceptions/validation_errors.rb +12 -2
- data/lib/grape/formatter/jsonl.rb +34 -0
- data/lib/grape/formatter/markdown.rb +17 -0
- data/lib/grape/formatter/md.rb +11 -0
- data/lib/grape/locale/en.yml +1 -0
- data/lib/grape/middleware/auth/base.rb +16 -1
- data/lib/grape/middleware/auth/strategy_info.rb +2 -2
- data/lib/grape/middleware/base.rb +19 -13
- data/lib/grape/middleware/error.rb +133 -35
- data/lib/grape/middleware/formatter.rb +235 -94
- data/lib/grape/middleware/precomputed_content_types.rb +14 -2
- data/lib/grape/middleware/stack.rb +7 -8
- data/lib/grape/middleware/versioner/base.rb +26 -10
- data/lib/grape/middleware/versioner/header.rb +70 -15
- data/lib/grape/middleware/versioner/param.rb +3 -5
- data/lib/grape/middleware/versioner/path.rb +20 -11
- data/lib/grape/middleware/versioner.rb +1 -1
- data/lib/grape/mountable.rb +2 -4
- data/lib/grape/namespace.rb +2 -3
- data/lib/grape/parser/jsonl.rb +19 -0
- data/lib/grape/request.rb +46 -8
- data/lib/grape/router/base_route.rb +41 -0
- data/lib/grape/router/greedy_route.rb +1 -1
- data/lib/grape/router/mustermann_pattern.rb +1 -1
- data/lib/grape/router/pattern/path.rb +1 -1
- data/lib/grape/router/pattern.rb +11 -5
- data/lib/grape/router/route.rb +42 -11
- data/lib/grape/router/route_buckets.rb +246 -0
- data/lib/grape/router.rb +262 -32
- data/lib/grape/util/endpoint_configuration.rb +7 -1
- data/lib/grape/util/inheritable_setting.rb +80 -161
- data/lib/grape/util/lazy/value.rb +52 -11
- data/lib/grape/util/media_type.rb +38 -15
- data/lib/grape/util/path_normalizer.rb +7 -2
- data/lib/grape/util/registry.rb +9 -2
- data/lib/grape/util/scrub.rb +33 -0
- data/lib/grape/util/translation.rb +6 -6
- data/lib/grape/validations/attributes_iterator.rb +73 -37
- data/lib/grape/validations/coerce_options.rb +1 -1
- data/lib/grape/validations/params_documentation.rb +15 -7
- data/lib/grape/validations/params_scope.rb +176 -43
- data/lib/grape/validations/types/custom_type_coercer.rb +2 -3
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +20 -12
- data/lib/grape/validations/types/multiple_type_coercer.rb +1 -2
- data/lib/grape/validations/types/primitive_coercer.rb +17 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +24 -10
- data/lib/grape/validations/types.rb +12 -18
- data/lib/grape/validations/validations_spec.rb +53 -22
- data/lib/grape/validations/validators/allow_blank_validator.rb +2 -2
- data/lib/grape/validations/validators/as_validator.rb +2 -3
- data/lib/grape/validations/validators/base.rb +124 -25
- data/lib/grape/validations/validators/coerce_validator.rb +2 -3
- data/lib/grape/validations/validators/default_validator.rb +18 -2
- data/lib/grape/validations/validators/except_values_validator.rb +16 -3
- data/lib/grape/validations/validators/hash_elements_validator.rb +34 -0
- data/lib/grape/validations/validators/length_validator.rb +24 -16
- data/lib/grape/validations/validators/multiple_params_base.rb +0 -4
- data/lib/grape/validations/validators/values_validator.rb +18 -24
- data/lib/grape/version.rb +1 -2
- data/lib/grape.rb +3 -0
- metadata +18 -11
- data/lib/grape/util/lazy/value_array.rb +0 -21
- data/lib/grape/util/lazy/value_enumerable.rb +0 -31
- data/lib/grape/util/lazy/value_hash.rb +0 -21
- data/lib/grape/validations/multiple_attributes_iterator.rb +0 -13
- data/lib/grape/validations/single_attribute_iterator.rb +0 -28
data/lib/grape/dsl/parameters.rb
CHANGED
|
@@ -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
|
|
9
|
+
# Set the builder used to build the request.params.
|
|
10
10
|
#
|
|
11
|
-
# @param build_with
|
|
12
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
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
|
-
#
|
|
103
|
+
# @example
|
|
108
104
|
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
105
|
+
# params do
|
|
106
|
+
# # Basic usage: require a parameter of a certain type
|
|
107
|
+
# requires :user_id, type: Integer
|
|
112
108
|
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
109
|
+
# # Without a type, the value is not coerced
|
|
110
|
+
# requires :foo
|
|
115
111
|
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
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
|
-
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
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
|
-
#
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
|
160
|
-
# validations
|
|
161
|
-
# @raise Grape::Exceptions::UnknownParameter if
|
|
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
|
-
#
|
|
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 =
|
|
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
|
-
#
|
|
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
|
-
#
|
|
102
|
-
#
|
|
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
|
-
|
|
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
|
data/lib/grape/dsl/routing.rb
CHANGED
|
@@ -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
|
|
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:
|
|
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,
|
|
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:, **
|
|
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.
|
|
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
|
-
# @
|
|
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
|
data/lib/grape/dsl/settings.rb
CHANGED
|
@@ -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
|
|
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
|