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
|
@@ -5,16 +5,16 @@ module Grape
|
|
|
5
5
|
class Txt < Base
|
|
6
6
|
def self.format_structured_message(structured_message)
|
|
7
7
|
message = structured_message[:message] || Grape::Json.dump(structured_message)
|
|
8
|
-
Array.wrap(message)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
final_message = Array.wrap(message)
|
|
9
|
+
if structured_message.key?(:backtrace)
|
|
10
|
+
final_message << 'backtrace:'
|
|
11
|
+
final_message.concat(structured_message[:backtrace])
|
|
12
|
+
end
|
|
13
|
+
if structured_message.key?(:original_exception)
|
|
14
|
+
final_message << 'original exception:'
|
|
15
|
+
final_message << structured_message[:original_exception]
|
|
16
|
+
end
|
|
17
|
+
final_message.join("\r\n ")
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
end
|
|
@@ -5,7 +5,7 @@ module Grape
|
|
|
5
5
|
class Base < StandardError
|
|
6
6
|
include Grape::Util::Translation
|
|
7
7
|
|
|
8
|
-
MESSAGE_STEPS = %w[problem summary resolution].to_h { |s| [s, s.capitalize] }.freeze
|
|
8
|
+
MESSAGE_STEPS = %w[problem summary resolution].to_h { |s| [s.to_sym, s.capitalize] }.freeze
|
|
9
9
|
|
|
10
10
|
attr_reader :status, :headers
|
|
11
11
|
|
|
@@ -27,6 +27,8 @@ module Grape
|
|
|
27
27
|
return short_message unless short_message.is_a?(Hash)
|
|
28
28
|
|
|
29
29
|
MESSAGE_STEPS.filter_map do |step, label|
|
|
30
|
+
next unless short_message.key?(step)
|
|
31
|
+
|
|
30
32
|
detail = translate_message(:"#{key}.#{step}", **)
|
|
31
33
|
"\n#{label}:\n #{detail}" if detail.present?
|
|
32
34
|
end.join
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Exceptions
|
|
5
|
+
# Value object representing the payload thrown via `throw :error, ...`
|
|
6
|
+
# and consumed by `Middleware::Error#error_response`. Replaces the
|
|
7
|
+
# implicit-schema Hash that previously circulated between throw sites
|
|
8
|
+
# and the error middleware.
|
|
9
|
+
ErrorResponse = Data.define(:status, :message, :headers, :backtrace, :original_exception) do
|
|
10
|
+
def initialize(status: nil, message: nil, headers: nil, backtrace: nil, original_exception: nil)
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_s
|
|
15
|
+
"#<#{self.class.name} status=#{status.inspect} message=#{message.inspect} headers=#{headers.inspect}>"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.from_exception(exception)
|
|
19
|
+
new(
|
|
20
|
+
status: exception.status,
|
|
21
|
+
message: exception.message,
|
|
22
|
+
headers: exception.headers,
|
|
23
|
+
backtrace: exception.backtrace,
|
|
24
|
+
original_exception: exception
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Normalize heterogeneous inputs into an ErrorResponse. Preserves the
|
|
29
|
+
# public contract that users can still `throw :error, hash` from their
|
|
30
|
+
# own middleware or `rescue_from` handlers.
|
|
31
|
+
def self.coerce(input)
|
|
32
|
+
case input
|
|
33
|
+
when ErrorResponse
|
|
34
|
+
input
|
|
35
|
+
when Grape::Exceptions::Base
|
|
36
|
+
from_exception(input)
|
|
37
|
+
when Hash
|
|
38
|
+
new(**input.slice(:status, :message, :headers, :backtrace, :original_exception))
|
|
39
|
+
else
|
|
40
|
+
new
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Exceptions
|
|
5
|
+
# Raised internally when a +rescue_from+ handler itself raises an
|
|
6
|
+
# unrecognised exception. The framework substitutes the original
|
|
7
|
+
# exception with this safe stand-in for rendering, while preserving
|
|
8
|
+
# the original on +env[Grape::Env::GRAPE_EXCEPTION]+ for upstream
|
|
9
|
+
# observability (loggers, error trackers, etc.).
|
|
10
|
+
class InternalServerError < Base
|
|
11
|
+
def initialize
|
|
12
|
+
super(status: 500, message: compose_message(:internal_server_error))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module Exceptions
|
|
5
5
|
class Validation < Base
|
|
6
|
+
EMPTY_BACKTRACE = [].freeze
|
|
7
|
+
|
|
6
8
|
attr_reader :params, :message_key
|
|
7
9
|
|
|
8
10
|
def initialize(params:, message: nil, status: nil, headers: nil)
|
|
@@ -16,6 +18,10 @@ module Grape
|
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
super(status:, message:, headers:)
|
|
21
|
+
# Pre-seed the backtrace so Ruby's raise skips capture. Validation errors are
|
|
22
|
+
# a hot path (raised per bad attribute) and end up as 400 Bad Request responses;
|
|
23
|
+
# backtraces here point into Grape internals and have no diagnostic value.
|
|
24
|
+
set_backtrace(EMPTY_BACKTRACE)
|
|
19
25
|
end
|
|
20
26
|
|
|
21
27
|
# Remove all the unnecessary stuff from Grape::Exceptions::Base like status
|
|
@@ -23,6 +29,14 @@ module Grape
|
|
|
23
29
|
def as_json(*_args)
|
|
24
30
|
to_s
|
|
25
31
|
end
|
|
32
|
+
|
|
33
|
+
# Returns +self+ so callers (e.g. +ValidationErrors#initialize+) can treat
|
|
34
|
+
# a single +Validation+ and a +ValidationArrayErrors+ wrapper uniformly via
|
|
35
|
+
# +flat_map(&:errors)+ — Array returns flatten in, non-Array returns
|
|
36
|
+
# (i.e. this +self+) append as one element.
|
|
37
|
+
def errors
|
|
38
|
+
self
|
|
39
|
+
end
|
|
26
40
|
end
|
|
27
41
|
end
|
|
28
42
|
end
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module Exceptions
|
|
5
5
|
class ValidationArrayErrors < Base
|
|
6
|
+
EMPTY_BACKTRACE = [].freeze
|
|
7
|
+
|
|
6
8
|
attr_reader :errors
|
|
7
9
|
|
|
8
10
|
def initialize(errors)
|
|
9
11
|
super()
|
|
10
12
|
@errors = errors
|
|
13
|
+
# Skip backtrace capture — see Grape::Exceptions::Validation for rationale.
|
|
14
|
+
set_backtrace(EMPTY_BACKTRACE)
|
|
11
15
|
end
|
|
12
16
|
end
|
|
13
17
|
end
|
|
@@ -3,23 +3,13 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module Exceptions
|
|
5
5
|
class ValidationErrors < Base
|
|
6
|
-
include Enumerable
|
|
7
|
-
|
|
8
6
|
attr_reader :errors
|
|
9
7
|
|
|
10
|
-
def initialize(
|
|
11
|
-
@errors = errors.group_by(&:params)
|
|
8
|
+
def initialize(exceptions: [], headers: {})
|
|
9
|
+
@errors = exceptions.flat_map(&:errors).group_by(&:params)
|
|
12
10
|
super(message: full_messages.join(', '), status: 400, headers:)
|
|
13
11
|
end
|
|
14
12
|
|
|
15
|
-
def each
|
|
16
|
-
errors.each_pair do |attribute, errors|
|
|
17
|
-
errors.each do |error|
|
|
18
|
-
yield attribute, error
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
13
|
def as_json(**_opts)
|
|
24
14
|
errors.map do |k, v|
|
|
25
15
|
{
|
|
@@ -34,14 +24,16 @@ module Grape
|
|
|
34
24
|
end
|
|
35
25
|
|
|
36
26
|
def full_messages
|
|
37
|
-
messages =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
27
|
+
messages = errors.flat_map do |attributes, errs|
|
|
28
|
+
errs.map do |error|
|
|
29
|
+
translate(
|
|
30
|
+
:format,
|
|
31
|
+
scope: 'grape.errors',
|
|
32
|
+
default: '%<attributes>s %<message>s',
|
|
33
|
+
attributes: translate_attributes(attributes),
|
|
34
|
+
message: error.message
|
|
35
|
+
)
|
|
36
|
+
end
|
|
45
37
|
end
|
|
46
38
|
messages.uniq!
|
|
47
39
|
messages
|
|
@@ -19,15 +19,11 @@ module Grape
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def serialize(object)
|
|
22
|
-
if object.respond_to?
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
object.transform_values { |v| serialize(v) }
|
|
28
|
-
else
|
|
29
|
-
object
|
|
30
|
-
end
|
|
22
|
+
return object.serializable_hash if object.respond_to?(:serializable_hash)
|
|
23
|
+
return object.map(&:serializable_hash) if array_serializable?(object)
|
|
24
|
+
return object.transform_values { |v| serialize(v) } if object.is_a?(Hash)
|
|
25
|
+
|
|
26
|
+
object
|
|
31
27
|
end
|
|
32
28
|
|
|
33
29
|
def array_serializable?(object)
|
data/lib/grape/json.rb
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
|
-
if defined?(::
|
|
5
|
-
|
|
4
|
+
if defined?(::MultiJSON)
|
|
5
|
+
# Since multi_json 1.21.0, MultiJSON.dump is deprecated in favor of
|
|
6
|
+
# MultiJSON.generate (removed in 2.0). Keep Grape's dump surface but route
|
|
7
|
+
# it to the non-deprecated name — identical output, no deprecation warning.
|
|
8
|
+
# https://github.com/sferik/multi_json/blob/v1.21.1/CHANGELOG.md#deprecated
|
|
9
|
+
module Json
|
|
10
|
+
ParseError = ::MultiJSON::ParseError
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def dump(object)
|
|
14
|
+
::MultiJSON.generate(object)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# parse is not deprecated; it's re-exposed (not renamed) because this
|
|
18
|
+
# facade is its own module and no longer inherits MultiJSON's methods.
|
|
19
|
+
def parse(source)
|
|
20
|
+
::MultiJSON.parse(source)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
elsif defined?(::MultiJson)
|
|
25
|
+
# Legacy multi_json (< 1.21) predates generate/parse and only exposes
|
|
26
|
+
# dump/load. Map Grape's surface onto them so the call sites stay
|
|
27
|
+
# engine-agnostic (these names are not deprecated on < 1.21).
|
|
28
|
+
module Json
|
|
29
|
+
# Mutually exclusive with the MultiJSON branch above; only one runs.
|
|
30
|
+
ParseError = ::MultiJson::ParseError # rubocop:disable Lint/ConstantReassignment
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
def dump(object)
|
|
34
|
+
::MultiJson.dump(object)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse(source)
|
|
38
|
+
::MultiJson.load(source)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
6
42
|
else
|
|
7
43
|
Json = ::JSON
|
|
8
44
|
Json::ParseError = Json::ParserError
|
data/lib/grape/locale/en.yml
CHANGED
|
@@ -11,6 +11,7 @@ en:
|
|
|
11
11
|
exactly_one: 'are missing, exactly one parameter must be provided'
|
|
12
12
|
except_values: 'has a value not allowed'
|
|
13
13
|
incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
|
|
14
|
+
internal_server_error: 'Internal Server Error'
|
|
14
15
|
invalid_accept_header:
|
|
15
16
|
problem: 'invalid accept header'
|
|
16
17
|
resolution: '%{message}'
|
|
@@ -42,6 +43,7 @@ en:
|
|
|
42
43
|
resolution: 'eg: version ''v1'', using: :header, vendor: ''twitter'''
|
|
43
44
|
summary: 'when version using header, you must specify :vendor option'
|
|
44
45
|
mutual_exclusion: 'are mutually exclusive'
|
|
46
|
+
oneof: 'does not match any of the allowed schemas'
|
|
45
47
|
presence: 'is missing'
|
|
46
48
|
regexp: 'is invalid'
|
|
47
49
|
same_as: 'is not the same as %{parameter}'
|
|
@@ -8,9 +8,8 @@ module Grape
|
|
|
8
8
|
super
|
|
9
9
|
return unless options.key?(:type)
|
|
10
10
|
|
|
11
|
-
@auth_strategy = Grape::Middleware::Auth::Strategies[options[:type]]
|
|
12
|
-
|
|
13
|
-
end
|
|
11
|
+
@auth_strategy = Grape::Middleware::Auth::Strategies[options[:type]]
|
|
12
|
+
raise Grape::Exceptions::UnknownAuthStrategy.new(strategy: options[:type]) unless @auth_strategy
|
|
14
13
|
end
|
|
15
14
|
|
|
16
15
|
def call!(env)
|
|
@@ -4,24 +4,27 @@ module Grape
|
|
|
4
4
|
module Middleware
|
|
5
5
|
module Auth
|
|
6
6
|
module DSL
|
|
7
|
-
def auth(type = nil, options
|
|
7
|
+
def auth(type = nil, *legacy_options, **options, &block)
|
|
8
8
|
namespace_inheritable = inheritable_setting.namespace_inheritable
|
|
9
9
|
return namespace_inheritable[:auth] unless type
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
options = merge_legacy_auth_options(:auth, legacy_options, options)
|
|
12
|
+
namespace_inheritable[:auth] = { type: type.to_sym, proc: block }.merge!(options)
|
|
12
13
|
use Grape::Middleware::Auth::Base, namespace_inheritable[:auth]
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
# Add HTTP Basic authorization to the API.
|
|
16
17
|
#
|
|
17
|
-
# @param [Hash]
|
|
18
|
-
# @option options [String] :realm "API Authorization"
|
|
19
|
-
def http_basic(options
|
|
18
|
+
# @param options [Hash] a hash of options
|
|
19
|
+
# @option options [String] :realm "API Authorization" the HTTP Basic realm
|
|
20
|
+
def http_basic(*legacy_options, **options, &)
|
|
21
|
+
options = merge_legacy_auth_options(:http_basic, legacy_options, options)
|
|
20
22
|
options[:realm] ||= 'API Authorization'
|
|
21
|
-
auth(:http_basic, options, &)
|
|
23
|
+
auth(:http_basic, **options, &)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
def http_digest(options
|
|
26
|
+
def http_digest(*legacy_options, **options, &)
|
|
27
|
+
options = merge_legacy_auth_options(:http_digest, legacy_options, options)
|
|
25
28
|
options[:realm] ||= 'API Authorization'
|
|
26
29
|
|
|
27
30
|
if options[:realm].respond_to?(:values_at)
|
|
@@ -30,7 +33,19 @@ module Grape
|
|
|
30
33
|
options[:opaque] ||= 'secret'
|
|
31
34
|
end
|
|
32
35
|
|
|
33
|
-
auth(:http_digest, options, &)
|
|
36
|
+
auth(:http_digest, **options, &)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# @deprecated Passing a positional options Hash is deprecated; pass
|
|
42
|
+
# keyword arguments instead. Kept so downstream callers keep working
|
|
43
|
+
# through the deprecation cycle.
|
|
44
|
+
def merge_legacy_auth_options(method_name, legacy_options, options)
|
|
45
|
+
return options if legacy_options.empty?
|
|
46
|
+
|
|
47
|
+
Grape.deprecator.warn("Passing a positional options Hash to `#{method_name}` is deprecated. Pass keyword arguments instead.")
|
|
48
|
+
legacy_options.first.merge(options)
|
|
34
49
|
end
|
|
35
50
|
end
|
|
36
51
|
end
|
|
@@ -5,13 +5,25 @@ module Grape
|
|
|
5
5
|
class Base
|
|
6
6
|
include Grape::DSL::Headers
|
|
7
7
|
|
|
8
|
-
attr_reader :app, :env, :options
|
|
8
|
+
attr_reader :app, :env, :options, :config
|
|
9
9
|
|
|
10
10
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
|
11
|
-
# @param [Hash] options
|
|
11
|
+
# @param [Hash] options Options forwarded to the subclass. When the
|
|
12
|
+
# subclass declares an `Options` Data class, the kwargs are routed
|
|
13
|
+
# through it and exposed via {#config}; {#options} keeps returning a
|
|
14
|
+
# frozen Hash representation for back-compat with subclasses that read
|
|
15
|
+
# `options[:key]`. Otherwise the kwargs are deep-merged with the
|
|
16
|
+
# subclass's `DEFAULT_OPTIONS` Hash (legacy path) and frozen.
|
|
12
17
|
def initialize(app, **options)
|
|
13
18
|
@app = app
|
|
14
|
-
|
|
19
|
+
if self.class.const_defined?(:Options)
|
|
20
|
+
# Search ancestors so subclasses (e.g. Versioner::Path → Versioner::Base)
|
|
21
|
+
# inherit their parent's Options Data class without redeclaring it.
|
|
22
|
+
@config = self.class::Options.new(**options)
|
|
23
|
+
@options = @config.to_h.freeze
|
|
24
|
+
else
|
|
25
|
+
@options = merge_default_options(options).freeze
|
|
26
|
+
end
|
|
15
27
|
@app_response = nil
|
|
16
28
|
end
|
|
17
29
|
|
|
@@ -61,22 +73,6 @@ module Grape
|
|
|
61
73
|
@app_response = Rack::Response[*@app_response]
|
|
62
74
|
end
|
|
63
75
|
|
|
64
|
-
def content_types
|
|
65
|
-
@content_types ||= Grape::ContentTypes.content_types_for(options[:content_types])
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def mime_types
|
|
69
|
-
@mime_types ||= Grape::ContentTypes.mime_types_for(content_types)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def content_type_for(format)
|
|
73
|
-
content_types_indifferent_access[format]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def content_type
|
|
77
|
-
content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || 'text/html'
|
|
78
|
-
end
|
|
79
|
-
|
|
80
76
|
def query_params
|
|
81
77
|
rack_request.GET
|
|
82
78
|
rescue *Grape::RACK_ERRORS
|
|
@@ -86,26 +82,19 @@ module Grape
|
|
|
86
82
|
private
|
|
87
83
|
|
|
88
84
|
def merge_headers(response)
|
|
89
|
-
return
|
|
85
|
+
return if @header.blank?
|
|
90
86
|
|
|
91
87
|
case response
|
|
92
|
-
when Rack::Response then response.headers.merge!(
|
|
93
|
-
when Array then response[1].merge!(
|
|
88
|
+
when Rack::Response then response.headers.merge!(@header)
|
|
89
|
+
when Array then response[1].merge!(@header)
|
|
94
90
|
end
|
|
95
91
|
end
|
|
96
92
|
|
|
97
|
-
def content_types_indifferent_access
|
|
98
|
-
@content_types_indifferent_access ||= content_types.with_indifferent_access
|
|
99
|
-
end
|
|
100
|
-
|
|
101
93
|
def merge_default_options(options)
|
|
102
|
-
if respond_to?(:default_options)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
else
|
|
107
|
-
options
|
|
108
|
-
end
|
|
94
|
+
return default_options.deep_merge(options) if respond_to?(:default_options)
|
|
95
|
+
return self.class::DEFAULT_OPTIONS.deep_merge(options) if self.class.const_defined?(:DEFAULT_OPTIONS)
|
|
96
|
+
|
|
97
|
+
options
|
|
109
98
|
end
|
|
110
99
|
|
|
111
100
|
def try_scrub(obj)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Middleware
|
|
5
|
+
# Mixin for per-middleware +Options+ +Data+ classes that need to keep
|
|
6
|
+
# accepting legacy +data[:key]+ Hash-style access while nudging callers
|
|
7
|
+
# toward the named accessor. Emits a +Grape.deprecator+ warning then
|
|
8
|
+
# forwards to +public_send(key)+.
|
|
9
|
+
module DeprecatedOptionsHashAccess
|
|
10
|
+
def [](key)
|
|
11
|
+
Grape.deprecator.warn(
|
|
12
|
+
"`#{self.class.name}#[]` is deprecated. " \
|
|
13
|
+
"Use the named accessor `#{key}` instead."
|
|
14
|
+
)
|
|
15
|
+
public_send(key) if members.include?(key)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|