grape 3.2.1 → 3.3.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 +80 -0
- data/README.md +116 -43
- data/UPGRADING.md +336 -1
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +7 -7
- data/lib/grape/api.rb +22 -25
- data/lib/grape/cookies.rb +2 -6
- data/lib/grape/declared_params_handler.rb +48 -50
- data/lib/grape/dsl/callbacks.rb +9 -3
- data/lib/grape/dsl/desc.rb +8 -2
- data/lib/grape/dsl/entity.rb +88 -0
- data/lib/grape/dsl/helpers.rb +27 -7
- data/lib/grape/dsl/inside_route.rb +38 -129
- data/lib/grape/dsl/logger.rb +3 -5
- data/lib/grape/dsl/parameters.rb +32 -38
- data/lib/grape/dsl/request_response.rb +53 -48
- data/lib/grape/dsl/rescue_options.rb +24 -0
- data/lib/grape/dsl/routing.rb +51 -35
- data/lib/grape/dsl/settings.rb +14 -8
- data/lib/grape/dsl/version_options.rb +23 -0
- data/lib/grape/endpoint/options.rb +19 -0
- data/lib/grape/endpoint.rb +96 -68
- data/lib/grape/env.rb +1 -3
- data/lib/grape/error_formatter/base.rb +23 -20
- data/lib/grape/error_formatter/json.rb +8 -4
- data/lib/grape/error_formatter/txt.rb +10 -10
- data/lib/grape/exceptions/base.rb +3 -1
- data/lib/grape/exceptions/error_response.rb +45 -0
- data/lib/grape/exceptions/internal_server_error.rb +16 -0
- data/lib/grape/exceptions/validation.rb +14 -0
- data/lib/grape/exceptions/validation_array_errors.rb +4 -0
- data/lib/grape/exceptions/validation_errors.rb +12 -20
- data/lib/grape/formatter/serializable_hash.rb +5 -9
- data/lib/grape/json.rb +38 -2
- data/lib/grape/locale/en.yml +2 -0
- data/lib/grape/middleware/auth/base.rb +2 -3
- data/lib/grape/middleware/auth/dsl.rb +23 -8
- data/lib/grape/middleware/base.rb +22 -33
- data/lib/grape/middleware/deprecated_options_hash_access.rb +19 -0
- data/lib/grape/middleware/error.rb +152 -62
- data/lib/grape/middleware/formatter.rb +66 -50
- data/lib/grape/middleware/precomputed_content_types.rb +46 -0
- data/lib/grape/middleware/stack.rb +5 -6
- data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
- data/lib/grape/middleware/versioner/base.rb +34 -38
- data/lib/grape/middleware/versioner/header.rb +3 -5
- data/lib/grape/middleware/versioner/path.rb +8 -3
- data/lib/grape/namespace.rb +3 -3
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +1 -1
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/path.rb +14 -17
- data/lib/grape/request.rb +15 -8
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern.rb +6 -10
- data/lib/grape/router.rb +28 -42
- data/lib/grape/serve_stream/file_body.rb +1 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +1 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/base_inheritable.rb +13 -16
- data/lib/grape/util/inheritable_setting.rb +44 -27
- data/lib/grape/util/inheritable_values.rb +7 -3
- data/lib/grape/util/lazy/base.rb +16 -0
- data/lib/grape/util/lazy/block.rb +2 -9
- data/lib/grape/util/lazy/value.rb +2 -9
- data/lib/grape/util/lazy/value_enumerable.rb +13 -16
- data/lib/grape/util/media_type.rb +1 -4
- data/lib/grape/util/path_normalizer.rb +34 -0
- data/lib/grape/util/registry.rb +1 -1
- data/lib/grape/util/stackable_values.rb +11 -8
- data/lib/grape/validations/attributes_iterator.rb +13 -13
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/oneof_collector.rb +39 -0
- data/lib/grape/validations/param_scope_tracker.rb +14 -9
- data/lib/grape/validations/params_documentation.rb +25 -23
- data/lib/grape/validations/params_scope.rb +54 -172
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/types/array_coercer.rb +2 -2
- data/lib/grape/validations/types/custom_type_coercer.rb +41 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +3 -3
- data/lib/grape/validations/types/primitive_coercer.rb +10 -5
- data/lib/grape/validations/types/set_coercer.rb +1 -1
- data/lib/grape/validations/types/variant_collection_coercer.rb +8 -0
- data/lib/grape/validations/types.rb +23 -30
- data/lib/grape/validations/validations_spec.rb +149 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +1 -1
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/base.rb +39 -22
- data/lib/grape/validations/validators/coerce_validator.rb +5 -3
- data/lib/grape/validations/validators/default_validator.rb +7 -8
- data/lib/grape/validations/validators/except_values_validator.rb +3 -2
- data/lib/grape/validations/validators/length_validator.rb +1 -1
- data/lib/grape/validations/validators/multiple_params_base.rb +10 -7
- data/lib/grape/validations/validators/oneof_validator.rb +49 -0
- data/lib/grape/validations/validators/values_validator.rb +5 -5
- data/lib/grape/version.rb +1 -1
- data/lib/grape/xml.rb +8 -1
- data/lib/grape.rb +6 -6
- metadata +34 -18
- data/lib/grape/middleware/globals.rb +0 -14
data/lib/grape/dsl/desc.rb
CHANGED
|
@@ -9,7 +9,8 @@ module Grape
|
|
|
9
9
|
# @param description [String] descriptive string for this endpoint
|
|
10
10
|
# or namespace
|
|
11
11
|
# @param options [Hash] other properties you can set to describe the
|
|
12
|
-
# endpoint or namespace. Optional.
|
|
12
|
+
# endpoint or namespace. Optional. Pass these as keyword arguments;
|
|
13
|
+
# passing a positional options Hash is deprecated.
|
|
13
14
|
# @option options :detail [String] additional detail about this endpoint
|
|
14
15
|
# @option options :summary [String] summary for this endpoint
|
|
15
16
|
# @option options :params [Hash] param types and info. normally, you set
|
|
@@ -49,7 +50,12 @@ module Grape
|
|
|
49
50
|
# # ...
|
|
50
51
|
# end
|
|
51
52
|
#
|
|
52
|
-
def desc(description, options
|
|
53
|
+
def desc(description, *legacy_options, **options, &config_block)
|
|
54
|
+
if legacy_options.any?
|
|
55
|
+
Grape.deprecator.warn('Passing a positional options Hash to `desc` is deprecated. Pass keyword arguments instead.')
|
|
56
|
+
options = legacy_options.first.merge(options)
|
|
57
|
+
end
|
|
58
|
+
|
|
53
59
|
settings =
|
|
54
60
|
if config_block
|
|
55
61
|
endpoint_config = defined?(configuration) ? configuration : nil
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module DSL
|
|
5
|
+
module Entity
|
|
6
|
+
# Allows you to make use of Grape Entities by setting
|
|
7
|
+
# the response body to the serializable hash of the
|
|
8
|
+
# entity provided in the `:with` option. This has the
|
|
9
|
+
# added benefit of automatically passing along environment
|
|
10
|
+
# and version information to the serialization, making it
|
|
11
|
+
# very easy to do conditional exposures. See Entity docs
|
|
12
|
+
# for more info.
|
|
13
|
+
#
|
|
14
|
+
# @param args [Array] either `(object)` or `(key, object)` where key is a Symbol
|
|
15
|
+
# used to nest the representation under that key in the response body.
|
|
16
|
+
# @param root [Symbol, String, nil] wraps the representation under this root key.
|
|
17
|
+
# @param with [Class, nil] the entity class to use for representation.
|
|
18
|
+
# If omitted, the entity class is inferred from the object via {#entity_class_for_obj}.
|
|
19
|
+
# @param options [Hash] additional options forwarded to the entity's `represent` call.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
#
|
|
23
|
+
# get '/users/:id' do
|
|
24
|
+
# present User.find(params[:id]),
|
|
25
|
+
# with: API::Entities::User,
|
|
26
|
+
# admin: current_user.admin?
|
|
27
|
+
# end
|
|
28
|
+
def present(*args, root: nil, with: nil, **options)
|
|
29
|
+
key, object = args.count == 2 && args.first.is_a?(Symbol) ? args : [nil, args.first]
|
|
30
|
+
entity_class = with || entity_class_for_obj(object)
|
|
31
|
+
representation = entity_class ? entity_representation_for(entity_class, object, options) : object
|
|
32
|
+
representation = { root => representation } if root
|
|
33
|
+
|
|
34
|
+
if key
|
|
35
|
+
representation = body&.merge(key => representation) || { key => representation }
|
|
36
|
+
elsif entity_class.present? && body
|
|
37
|
+
raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
|
|
38
|
+
|
|
39
|
+
representation = body.merge(representation)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
body representation
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Attempt to locate the Entity class for a given object, if not given
|
|
46
|
+
# explicitly. This is done by looking for the presence of Klass::Entity,
|
|
47
|
+
# where Klass is the class of the `object` parameter, or one of its
|
|
48
|
+
# ancestors.
|
|
49
|
+
# @param object [Object] the object to locate the Entity class for
|
|
50
|
+
# @return [Class] the located Entity class, or nil if none is found
|
|
51
|
+
def entity_class_for_obj(object)
|
|
52
|
+
klass = object_class(object)
|
|
53
|
+
|
|
54
|
+
representations = inheritable_setting.namespace_stackable_with_hash(:representations)
|
|
55
|
+
if representations
|
|
56
|
+
potential = klass.ancestors.detect { |potential| representations.key?(potential) }
|
|
57
|
+
return representations[potential] if potential && representations[potential]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
return unless klass.const_defined?(:Entity)
|
|
61
|
+
|
|
62
|
+
entity = klass.const_get(:Entity)
|
|
63
|
+
entity if entity.respond_to?(:represent)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Resolves the class used to look up the Entity for +object+.
|
|
69
|
+
# @param object [Object] the object to represent.
|
|
70
|
+
# @return [Class] the object's collection element class, wrapped class, or its own class.
|
|
71
|
+
def object_class(object)
|
|
72
|
+
return object.klass if object.respond_to?(:klass)
|
|
73
|
+
return object.first.class if object.respond_to?(:first)
|
|
74
|
+
|
|
75
|
+
object.class
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param entity_class [Class] the entity class to use for representation.
|
|
79
|
+
# @param object [Object] the object to represent.
|
|
80
|
+
# @param options [Hash] additional options forwarded to the entity's `represent` call.
|
|
81
|
+
# @return the representation of the given object as done through the given entity_class.
|
|
82
|
+
def entity_representation_for(entity_class, object, options)
|
|
83
|
+
embeds = env.key?(Grape::Env::API_VERSION) ? { env:, version: env[Grape::Env::API_VERSION] } : { env: }
|
|
84
|
+
entity_class.represent(object, **embeds, **options)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/grape/dsl/helpers.rb
CHANGED
|
@@ -45,9 +45,8 @@ module Grape
|
|
|
45
45
|
def include_block(block)
|
|
46
46
|
return unless block
|
|
47
47
|
|
|
48
|
-
Module.new
|
|
49
|
-
|
|
50
|
-
end
|
|
48
|
+
mod = Module.new
|
|
49
|
+
make_inclusion(mod) { mod.class_eval(&block) }
|
|
51
50
|
end
|
|
52
51
|
|
|
53
52
|
def make_inclusion(mod, &)
|
|
@@ -57,10 +56,10 @@ module Grape
|
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
def include_all_in_scope
|
|
60
|
-
Module.new
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
mod = Module.new
|
|
60
|
+
namespace_stackable(:helpers).each { |mod_to_include| mod.include mod_to_include }
|
|
61
|
+
change!
|
|
62
|
+
mod
|
|
64
63
|
end
|
|
65
64
|
|
|
66
65
|
def define_boolean_in_mod(mod)
|
|
@@ -72,9 +71,30 @@ module Grape
|
|
|
72
71
|
def inject_api_helpers_to_mod(mod, &block)
|
|
73
72
|
mod.extend(BaseHelper) unless mod.is_a?(BaseHelper)
|
|
74
73
|
yield if block
|
|
74
|
+
warn_on_endpoint_overrides(mod) if Grape.config.warn_on_helper_overrides
|
|
75
75
|
mod.api_changed(self)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
# When +Grape.config.warn_on_helper_overrides+ is enabled, emit a
|
|
79
|
+
# warning to +$stderr+ for any helper method that masks an instance
|
|
80
|
+
# method on +Grape::Endpoint+. Helpers are mixed into the endpoint's
|
|
81
|
+
# singleton class and therefore take precedence over +Endpoint+
|
|
82
|
+
# instance methods — usually intentional, but a common source of
|
|
83
|
+
# surprise when the framework gains a method that already collides
|
|
84
|
+
# with an existing helper name.
|
|
85
|
+
def warn_on_endpoint_overrides(mod)
|
|
86
|
+
overridden = mod.instance_methods(false).select { |m| Grape::Endpoint.method_defined?(m) }
|
|
87
|
+
return if overridden.empty?
|
|
88
|
+
|
|
89
|
+
overridden.each do |name|
|
|
90
|
+
warn(
|
|
91
|
+
"Grape: helper method `#{name}` overrides Grape::Endpoint##{name}. " \
|
|
92
|
+
'The helper takes precedence. To use the framework implementation, remove the helper. ' \
|
|
93
|
+
'Silence this warning by setting Grape.config.warn_on_helper_overrides = false.'
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
78
98
|
# This module extends user defined helpers
|
|
79
99
|
# to provide some API-specific functionality.
|
|
80
100
|
module BaseHelper
|
|
@@ -4,6 +4,7 @@ module Grape
|
|
|
4
4
|
module DSL
|
|
5
5
|
module InsideRoute
|
|
6
6
|
include Declared
|
|
7
|
+
include Entity
|
|
7
8
|
|
|
8
9
|
# Backward compatibility: alias exception class to previous location
|
|
9
10
|
MethodNotYetAvailable = Declared::MethodNotYetAvailable
|
|
@@ -14,7 +15,7 @@ module Grape
|
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def configuration
|
|
17
|
-
|
|
18
|
+
config.for.configuration.evaluate
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
# End the request and display an error to the
|
|
@@ -28,12 +29,9 @@ module Grape
|
|
|
28
29
|
def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
|
|
29
30
|
status = self.status(status || inheritable_setting.namespace_inheritable[:default_error_status])
|
|
30
31
|
headers = additional_headers.present? ? header.merge(additional_headers) : header
|
|
31
|
-
throw :error,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
headers:,
|
|
35
|
-
backtrace:,
|
|
36
|
-
original_exception:
|
|
32
|
+
throw :error, Grape::Exceptions::ErrorResponse.new(
|
|
33
|
+
message:, status:, headers:, backtrace:, original_exception:
|
|
34
|
+
)
|
|
37
35
|
end
|
|
38
36
|
|
|
39
37
|
# Redirect to a new url.
|
|
@@ -62,27 +60,11 @@ module Grape
|
|
|
62
60
|
#
|
|
63
61
|
# @param status [Integer] The HTTP Status Code to return for this request.
|
|
64
62
|
def status(status = nil)
|
|
65
|
-
|
|
66
|
-
when Symbol
|
|
67
|
-
raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
|
|
63
|
+
return @status || default_status if status.nil?
|
|
68
64
|
|
|
65
|
+
case status
|
|
66
|
+
when Symbol, Integer
|
|
69
67
|
@status = Rack::Utils.status_code(status)
|
|
70
|
-
when Integer
|
|
71
|
-
@status = status
|
|
72
|
-
when nil
|
|
73
|
-
return @status if @status
|
|
74
|
-
|
|
75
|
-
if request.post?
|
|
76
|
-
201
|
|
77
|
-
elsif request.delete?
|
|
78
|
-
if @body.present?
|
|
79
|
-
200
|
|
80
|
-
else
|
|
81
|
-
204
|
|
82
|
-
end
|
|
83
|
-
else
|
|
84
|
-
200
|
|
85
|
-
end
|
|
86
68
|
else
|
|
87
69
|
raise ArgumentError, 'Status code must be Integer or Symbol.'
|
|
88
70
|
end
|
|
@@ -90,11 +72,9 @@ module Grape
|
|
|
90
72
|
|
|
91
73
|
# Set response content-type
|
|
92
74
|
def content_type(val = nil)
|
|
93
|
-
if val
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
header[Rack::CONTENT_TYPE]
|
|
97
|
-
end
|
|
75
|
+
return header(Rack::CONTENT_TYPE, val) if val
|
|
76
|
+
|
|
77
|
+
header[Rack::CONTENT_TYPE]
|
|
98
78
|
end
|
|
99
79
|
|
|
100
80
|
# Allows you to define the response body as something other than the
|
|
@@ -128,7 +108,6 @@ module Grape
|
|
|
128
108
|
#
|
|
129
109
|
# DELETE /12 # => 204 No Content, ""
|
|
130
110
|
def return_no_content
|
|
131
|
-
status 204
|
|
132
111
|
body false
|
|
133
112
|
end
|
|
134
113
|
|
|
@@ -141,14 +120,12 @@ module Grape
|
|
|
141
120
|
#
|
|
142
121
|
# GET /file # => "contents of file"
|
|
143
122
|
def sendfile(value = nil)
|
|
144
|
-
if value.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
stream
|
|
151
|
-
end
|
|
123
|
+
return stream if value.nil?
|
|
124
|
+
|
|
125
|
+
raise ArgumentError, 'Argument must be a file path' unless value.is_a?(String)
|
|
126
|
+
|
|
127
|
+
file_body = Grape::ServeStream::FileBody.new(value)
|
|
128
|
+
@stream = Grape::ServeStream::StreamResponse.new(file_body)
|
|
152
129
|
end
|
|
153
130
|
|
|
154
131
|
# Allows you to define the response as a streamable object.
|
|
@@ -172,60 +149,10 @@ module Grape
|
|
|
172
149
|
header Rack::CONTENT_LENGTH, nil
|
|
173
150
|
header 'Transfer-Encoding', nil
|
|
174
151
|
header Rack::CACHE_CONTROL, 'no-cache' # Skips ETag generation (reading the response up front)
|
|
175
|
-
if value.is_a?(String)
|
|
176
|
-
file_body = Grape::ServeStream::FileBody.new(value)
|
|
177
|
-
@stream = Grape::ServeStream::StreamResponse.new(file_body)
|
|
178
|
-
elsif value.respond_to?(:each)
|
|
179
|
-
@stream = Grape::ServeStream::StreamResponse.new(value)
|
|
180
|
-
elsif !value.is_a?(NilClass)
|
|
181
|
-
raise ArgumentError, 'Stream object must respond to :each.'
|
|
182
|
-
else
|
|
183
|
-
@stream
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Allows you to make use of Grape Entities by setting
|
|
188
|
-
# the response body to the serializable hash of the
|
|
189
|
-
# entity provided in the `:with` option. This has the
|
|
190
|
-
# added benefit of automatically passing along environment
|
|
191
|
-
# and version information to the serialization, making it
|
|
192
|
-
# very easy to do conditional exposures. See Entity docs
|
|
193
|
-
# for more info.
|
|
194
|
-
#
|
|
195
|
-
# @example
|
|
196
|
-
#
|
|
197
|
-
# get '/users/:id' do
|
|
198
|
-
# present User.find(params[:id]),
|
|
199
|
-
# with: API::Entities::User,
|
|
200
|
-
# admin: current_user.admin?
|
|
201
|
-
# end
|
|
202
|
-
def present(*args, **options)
|
|
203
|
-
key, object = if args.count == 2 && args.first.is_a?(Symbol)
|
|
204
|
-
args
|
|
205
|
-
else
|
|
206
|
-
[nil, args.first]
|
|
207
|
-
end
|
|
208
|
-
entity_class = entity_class_for_obj(object, options)
|
|
209
|
-
|
|
210
|
-
root = options.delete(:root)
|
|
211
|
-
|
|
212
|
-
representation = if entity_class
|
|
213
|
-
entity_representation_for(entity_class, object, options)
|
|
214
|
-
else
|
|
215
|
-
object
|
|
216
|
-
end
|
|
217
152
|
|
|
218
|
-
|
|
153
|
+
return @stream if value.nil?
|
|
219
154
|
|
|
220
|
-
|
|
221
|
-
representation = (body || {}).merge(key => representation)
|
|
222
|
-
elsif entity_class.present? && body
|
|
223
|
-
raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
|
|
224
|
-
|
|
225
|
-
representation = body.merge(representation)
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
body representation
|
|
155
|
+
@stream = Grape::ServeStream::StreamResponse.new(stream_body(value))
|
|
229
156
|
end
|
|
230
157
|
|
|
231
158
|
# Returns route information for the current request.
|
|
@@ -240,43 +167,6 @@ module Grape
|
|
|
240
167
|
env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
|
|
241
168
|
end
|
|
242
169
|
|
|
243
|
-
# Attempt to locate the Entity class for a given object, if not given
|
|
244
|
-
# explicitly. This is done by looking for the presence of Klass::Entity,
|
|
245
|
-
# where Klass is the class of the `object` parameter, or one of its
|
|
246
|
-
# ancestors.
|
|
247
|
-
# @param object [Object] the object to locate the Entity class for
|
|
248
|
-
# @param options [Hash]
|
|
249
|
-
# @option options :with [Class] the explicit entity class to use
|
|
250
|
-
# @return [Class] the located Entity class, or nil if none is found
|
|
251
|
-
def entity_class_for_obj(object, options)
|
|
252
|
-
entity_class = options.delete(:with)
|
|
253
|
-
return entity_class if entity_class
|
|
254
|
-
|
|
255
|
-
# entity class not explicitly defined, auto-detect from relation#klass or first object in the collection
|
|
256
|
-
object_class = if object.respond_to?(:klass)
|
|
257
|
-
object.klass
|
|
258
|
-
else
|
|
259
|
-
object.respond_to?(:first) ? object.first.class : object.class
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
representations = inheritable_setting.namespace_stackable_with_hash(:representations)
|
|
263
|
-
if representations
|
|
264
|
-
potential = object_class.ancestors.detect { |potential| representations.key?(potential) }
|
|
265
|
-
entity_class = representations[potential] if potential
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
entity_class = object_class.const_get(:Entity) if !entity_class && object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
|
|
269
|
-
entity_class
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
# @return the representation of the given object as done through
|
|
273
|
-
# the given entity_class.
|
|
274
|
-
def entity_representation_for(entity_class, object, options)
|
|
275
|
-
embeds = { env: }
|
|
276
|
-
embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
|
|
277
|
-
entity_class.represent(object, **embeds, **options)
|
|
278
|
-
end
|
|
279
|
-
|
|
280
170
|
def http_version
|
|
281
171
|
env.fetch('HTTP_VERSION') { env[Rack::SERVER_PROTOCOL] }
|
|
282
172
|
end
|
|
@@ -288,6 +178,25 @@ module Grape
|
|
|
288
178
|
def context
|
|
289
179
|
self
|
|
290
180
|
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
# Wraps a stream +value+ into a body that responds to +:each+.
|
|
185
|
+
def stream_body(value)
|
|
186
|
+
return Grape::ServeStream::FileBody.new(value) if value.is_a?(String)
|
|
187
|
+
|
|
188
|
+
raise ArgumentError, 'Stream object must respond to :each.' unless value.respond_to?(:each)
|
|
189
|
+
|
|
190
|
+
value
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# The default HTTP status when none has been set explicitly.
|
|
194
|
+
def default_status
|
|
195
|
+
return 201 if request.post?
|
|
196
|
+
return 204 if request.delete? && @body.blank?
|
|
197
|
+
|
|
198
|
+
200
|
|
199
|
+
end
|
|
291
200
|
end
|
|
292
201
|
end
|
|
293
202
|
end
|
data/lib/grape/dsl/logger.rb
CHANGED
|
@@ -8,11 +8,9 @@ module Grape
|
|
|
8
8
|
# @param logger [Object] the new logger to use
|
|
9
9
|
def logger(logger = nil)
|
|
10
10
|
global_settings = inheritable_setting.global
|
|
11
|
-
if logger
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
global_settings[:logger] || global_settings[:logger] = ::Logger.new($stdout)
|
|
15
|
-
end
|
|
11
|
+
return global_settings[:logger] = logger if logger
|
|
12
|
+
|
|
13
|
+
global_settings[:logger] || global_settings[:logger] = ::Logger.new($stdout)
|
|
16
14
|
end
|
|
17
15
|
end
|
|
18
16
|
end
|
data/lib/grape/dsl/parameters.rb
CHANGED
|
@@ -122,23 +122,21 @@ module Grape
|
|
|
122
122
|
# requires :name, type: String
|
|
123
123
|
# end
|
|
124
124
|
# end
|
|
125
|
-
def requires(*attrs, **opts, &block)
|
|
125
|
+
def requires(*attrs, using: nil, except: nil, **opts, &block)
|
|
126
126
|
opts[:presence] = { value: true, message: opts[:message] }
|
|
127
127
|
opts = @group.deep_merge(opts) if @group
|
|
128
128
|
|
|
129
|
-
if
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], &block) : push_declared_params(attrs, as: opts[:as])
|
|
134
|
-
end
|
|
129
|
+
return require_required_and_optional_fields(attrs.first, using:, except:) if using
|
|
130
|
+
|
|
131
|
+
validate_attributes(attrs, **opts, &block)
|
|
132
|
+
block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], &block) : push_declared_params(attrs, as: opts[:as])
|
|
135
133
|
end
|
|
136
134
|
|
|
137
135
|
# Allow, but don't require, one or more parameters for the current
|
|
138
136
|
# endpoint.
|
|
139
137
|
# @param (see #requires)
|
|
140
138
|
# @option (see #requires)
|
|
141
|
-
def optional(*attrs, **opts, &block)
|
|
139
|
+
def optional(*attrs, using: nil, except: nil, **opts, &block)
|
|
142
140
|
type = opts[:type]
|
|
143
141
|
opts = @group.deep_merge(opts) if @group
|
|
144
142
|
|
|
@@ -148,13 +146,10 @@ module Grape
|
|
|
148
146
|
raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
|
|
149
147
|
end
|
|
150
148
|
|
|
151
|
-
if
|
|
152
|
-
require_optional_fields(attrs.first, using: opts[:using], except: opts[:except])
|
|
153
|
-
else
|
|
154
|
-
validate_attributes(attrs, **opts, &block)
|
|
149
|
+
return require_optional_fields(attrs.first, using:, except:) if using
|
|
155
150
|
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
validate_attributes(attrs, **opts, &block)
|
|
152
|
+
block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], optional: true, &block) : push_declared_params(attrs, as: opts[:as])
|
|
158
153
|
end
|
|
159
154
|
|
|
160
155
|
# Define common settings for one or more parameters
|
|
@@ -190,35 +185,19 @@ module Grape
|
|
|
190
185
|
# block yet.
|
|
191
186
|
# @return [Boolean] whether the parameter has been defined
|
|
192
187
|
def declared_param?(param)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
first_hash_key_or_param(declared_param_attr.key) == param
|
|
201
|
-
end
|
|
188
|
+
# Elements of @declared_params of lateral scope are pushed in @parent. So check them in @parent.
|
|
189
|
+
return @parent.declared_param?(param) if lateral?
|
|
190
|
+
|
|
191
|
+
# @declared_params also includes hashes of options and such, but those
|
|
192
|
+
# won't be flattened out.
|
|
193
|
+
@declared_params.flatten.any? do |declared_param_attr|
|
|
194
|
+
first_hash_key_or_param(declared_param_attr.key) == param
|
|
202
195
|
end
|
|
203
196
|
end
|
|
204
197
|
|
|
205
198
|
alias group requires
|
|
206
199
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def map_params(params, element, is_array = false)
|
|
210
|
-
if params.is_a?(Array)
|
|
211
|
-
params.map do |el|
|
|
212
|
-
map_params(el, element, true)
|
|
213
|
-
end
|
|
214
|
-
elsif params.is_a?(Hash)
|
|
215
|
-
params[element] || (@optional && is_array ? EmptyOptionalValue : {})
|
|
216
|
-
elsif params == EmptyOptionalValue
|
|
217
|
-
EmptyOptionalValue
|
|
218
|
-
else
|
|
219
|
-
{}
|
|
220
|
-
end
|
|
221
|
-
end
|
|
200
|
+
EmptyOptionalValue = Object.new.freeze
|
|
222
201
|
|
|
223
202
|
# @param params [Hash] initial hash of parameters
|
|
224
203
|
# @return hash of parameters relevant for the current scope
|
|
@@ -234,6 +213,21 @@ module Grape
|
|
|
234
213
|
def first_hash_key_or_param(parameter)
|
|
235
214
|
parameter.is_a?(Hash) ? parameter.keys.first : parameter
|
|
236
215
|
end
|
|
216
|
+
|
|
217
|
+
def map_params(params, element, is_array: false)
|
|
218
|
+
case params
|
|
219
|
+
when Array
|
|
220
|
+
params.map do |el|
|
|
221
|
+
map_params(el, element, is_array: true)
|
|
222
|
+
end
|
|
223
|
+
when Hash
|
|
224
|
+
params[element] || (@optional && is_array ? EmptyOptionalValue : {})
|
|
225
|
+
when EmptyOptionalValue
|
|
226
|
+
EmptyOptionalValue
|
|
227
|
+
else
|
|
228
|
+
{}
|
|
229
|
+
end
|
|
230
|
+
end
|
|
237
231
|
end
|
|
238
232
|
end
|
|
239
233
|
end
|
|
@@ -44,13 +44,8 @@ module Grape
|
|
|
44
44
|
inheritable_setting.namespace_inheritable[:default_error_formatter] = new_formatter
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
def error_formatter(format, options)
|
|
48
|
-
formatter =
|
|
49
|
-
options[:with]
|
|
50
|
-
else
|
|
51
|
-
options
|
|
52
|
-
end
|
|
53
|
-
|
|
47
|
+
def error_formatter(format, options = nil, with: nil)
|
|
48
|
+
formatter = with || options
|
|
54
49
|
inheritable_setting.namespace_stackable[:error_formatters] = { format.to_sym => formatter }
|
|
55
50
|
end
|
|
56
51
|
|
|
@@ -84,46 +79,52 @@ module Grape
|
|
|
84
79
|
# rescue_from CustomError
|
|
85
80
|
# end
|
|
86
81
|
#
|
|
82
|
+
META_RESCUE_SELECTORS = %i[all grape_exceptions internal_grape_exceptions].freeze
|
|
83
|
+
private_constant :META_RESCUE_SELECTORS
|
|
84
|
+
|
|
87
85
|
# @overload rescue_from(*exception_classes, **options)
|
|
88
86
|
# @param [Array] exception_classes A list of classes that you want to rescue, or
|
|
89
|
-
#
|
|
87
|
+
# one of the meta selectors +:all+, +:grape_exceptions+,
|
|
88
|
+
# +:internal_grape_exceptions+. Meta selectors must be used alone;
|
|
89
|
+
# mixing with exception classes raises +ArgumentError+.
|
|
90
90
|
# @param [Block] block Execution block to handle the given exception.
|
|
91
|
-
# @param [
|
|
92
|
-
#
|
|
93
|
-
# @
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
# @param [Proc] with Execution proc to handle the given exception as an alternative
|
|
92
|
+
# to passing a block.
|
|
93
|
+
# @param [Boolean] rescue_subclasses Also rescue subclasses of exception classes;
|
|
94
|
+
# defaults to +true+.
|
|
95
|
+
# @param [Boolean] backtrace Include the rescued exception's backtrace in the
|
|
96
|
+
# rescue response body.
|
|
97
|
+
# @param [Boolean] original_exception Include +inspect+ of the rescued exception
|
|
98
|
+
# in the rescue response body.
|
|
99
|
+
def rescue_from(*args, with: nil, rescue_subclasses: true, backtrace: false, original_exception: false, &block)
|
|
100
|
+
handler = extract_handler(args, with:, block:)
|
|
101
|
+
meta_selector = (args & META_RESCUE_SELECTORS).first
|
|
102
|
+
raise ArgumentError, "rescue_from #{meta_selector.inspect} does not accept additional arguments" if meta_selector && args.size > 1
|
|
103
|
+
|
|
104
|
+
namespace_inheritable = nil
|
|
105
|
+
arg = nil
|
|
106
|
+
|
|
107
|
+
if args.one?
|
|
108
|
+
arg = args.first
|
|
109
|
+
namespace_inheritable = inheritable_setting.namespace_inheritable
|
|
101
110
|
end
|
|
102
111
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler] = handler
|
|
112
|
+
case arg
|
|
113
|
+
when :all
|
|
114
|
+
namespace_inheritable[:rescue_all] = true
|
|
115
|
+
namespace_inheritable[:all_rescue_handler] = handler
|
|
116
|
+
when :grape_exceptions
|
|
117
|
+
namespace_inheritable[:rescue_all] = true
|
|
118
|
+
namespace_inheritable[:rescue_grape_exceptions] = true
|
|
119
|
+
namespace_inheritable[:grape_exceptions_rescue_handler] = handler
|
|
120
|
+
when :internal_grape_exceptions
|
|
121
|
+
namespace_inheritable[:internal_grape_exceptions_rescue_handler] = handler
|
|
114
122
|
else
|
|
115
|
-
handler_type =
|
|
116
|
-
|
|
117
|
-
when nil, true
|
|
118
|
-
:rescue_handlers
|
|
119
|
-
else
|
|
120
|
-
:base_only_rescue_handlers
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
inheritable_setting.namespace_reverse_stackable[handler_type] = args.to_h { |arg| [arg, handler] }
|
|
123
|
+
handler_type = rescue_subclasses ? :rescue_handlers : :base_only_rescue_handlers
|
|
124
|
+
inheritable_setting.namespace_reverse_stackable[handler_type] = args.to_h { |klass| [klass, handler] }
|
|
124
125
|
end
|
|
125
126
|
|
|
126
|
-
inheritable_setting.namespace_stackable[:rescue_options] =
|
|
127
|
+
inheritable_setting.namespace_stackable[:rescue_options] = RescueOptions.new(backtrace:, original_exception:)
|
|
127
128
|
end
|
|
128
129
|
|
|
129
130
|
# Allows you to specify a default representation entity for a
|
|
@@ -146,22 +147,26 @@ module Grape
|
|
|
146
147
|
#
|
|
147
148
|
# @param model_class [Class] The model class that will be represented.
|
|
148
149
|
# @option options [Class] :with The entity class that will represent the model.
|
|
149
|
-
def represent(model_class,
|
|
150
|
-
raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless
|
|
150
|
+
def represent(model_class, with:)
|
|
151
|
+
raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless with.is_a?(Class)
|
|
151
152
|
|
|
152
|
-
inheritable_setting.namespace_stackable[:representations] = { model_class =>
|
|
153
|
+
inheritable_setting.namespace_stackable[:representations] = { model_class => with }
|
|
153
154
|
end
|
|
154
155
|
|
|
155
156
|
private
|
|
156
157
|
|
|
157
|
-
def
|
|
158
|
-
|
|
158
|
+
def extract_handler(args, with:, block:)
|
|
159
|
+
raise ArgumentError, 'both :with option and block cannot be passed' if block && with
|
|
159
160
|
|
|
160
|
-
|
|
161
|
-
return
|
|
162
|
-
return
|
|
161
|
+
return args.pop if args.last.is_a?(Proc)
|
|
162
|
+
return block if block
|
|
163
|
+
return unless with
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
case with
|
|
166
|
+
when Proc, Symbol then with
|
|
167
|
+
when String then with.to_sym
|
|
168
|
+
else raise ArgumentError, "with: #{with.class}, expected Symbol, String or Proc"
|
|
169
|
+
end
|
|
165
170
|
end
|
|
166
171
|
end
|
|
167
172
|
end
|