grape 0.1.1 → 2.4.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 +7 -0
- data/CHANGELOG.md +1167 -0
- data/CONTRIBUTING.md +156 -0
- data/LICENSE +1 -1
- data/README.md +4192 -0
- data/UPGRADING.md +1697 -0
- data/grape.gemspec +28 -105
- data/grape.png +0 -0
- data/lib/grape/api/helpers.rb +9 -0
- data/lib/grape/api/instance.rb +247 -0
- data/lib/grape/api.rb +158 -194
- data/lib/grape/content_types.rb +31 -0
- data/lib/grape/cookies.rb +50 -0
- data/lib/grape/dry_types.rb +10 -0
- data/lib/grape/dsl/api.rb +17 -0
- data/lib/grape/dsl/callbacks.rb +69 -0
- data/lib/grape/dsl/configuration.rb +15 -0
- data/lib/grape/dsl/desc.rb +124 -0
- data/lib/grape/dsl/headers.rb +21 -0
- data/lib/grape/dsl/helpers.rb +108 -0
- data/lib/grape/dsl/inside_route.rb +454 -0
- data/lib/grape/dsl/logger.rb +22 -0
- data/lib/grape/dsl/middleware.rb +54 -0
- data/lib/grape/dsl/parameters.rb +266 -0
- data/lib/grape/dsl/request_response.rb +171 -0
- data/lib/grape/dsl/routing.rb +248 -0
- data/lib/grape/dsl/settings.rb +179 -0
- data/lib/grape/dsl/validations.rb +57 -0
- data/lib/grape/endpoint.rb +398 -68
- data/lib/grape/env.rb +20 -0
- data/lib/grape/error_formatter/base.rb +68 -0
- data/lib/grape/error_formatter/json.rb +28 -0
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +21 -0
- data/lib/grape/error_formatter/xml.rb +11 -0
- data/lib/grape/error_formatter.rb +15 -0
- data/lib/grape/exceptions/base.rb +76 -0
- data/lib/grape/exceptions/conflicting_types.rb +11 -0
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +11 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +11 -0
- data/lib/grape/exceptions/invalid_formatter.rb +11 -0
- data/lib/grape/exceptions/invalid_message_body.rb +11 -0
- data/lib/grape/exceptions/invalid_parameters.rb +11 -0
- data/lib/grape/exceptions/invalid_response.rb +11 -0
- data/lib/grape/exceptions/invalid_version_header.rb +11 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +11 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +11 -0
- data/lib/grape/exceptions/method_not_allowed.rb +11 -0
- data/lib/grape/exceptions/missing_group_type.rb +13 -0
- data/lib/grape/exceptions/missing_mime_type.rb +11 -0
- data/lib/grape/exceptions/missing_option.rb +11 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +11 -0
- data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
- data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_options.rb +11 -0
- data/lib/grape/exceptions/unknown_parameter.rb +11 -0
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/unknown_validator.rb +11 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +13 -0
- data/lib/grape/exceptions/validation.rb +25 -0
- data/lib/grape/exceptions/validation_array_errors.rb +14 -0
- data/lib/grape/exceptions/validation_errors.rb +53 -0
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +24 -0
- data/lib/grape/extensions/hash.rb +27 -0
- data/lib/grape/extensions/hashie/mash.rb +24 -0
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +13 -0
- data/lib/grape/formatter/serializable_hash.rb +39 -0
- data/lib/grape/formatter/txt.rb +11 -0
- data/lib/grape/formatter/xml.rb +13 -0
- data/lib/grape/formatter.rb +17 -0
- data/lib/grape/json.rb +10 -0
- data/lib/grape/locale/en.yml +59 -0
- data/lib/grape/middleware/auth/base.rb +23 -0
- data/lib/grape/middleware/auth/dsl.rb +39 -0
- data/lib/grape/middleware/auth/strategies.rb +25 -0
- data/lib/grape/middleware/auth/strategy_info.rb +15 -0
- data/lib/grape/middleware/base.rb +89 -18
- data/lib/grape/middleware/error.rb +129 -10
- data/lib/grape/middleware/filter.rb +19 -0
- data/lib/grape/middleware/formatter.rb +124 -82
- data/lib/grape/middleware/globals.rb +14 -0
- data/lib/grape/middleware/stack.rb +109 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +38 -0
- data/lib/grape/middleware/versioner/base.rb +74 -0
- data/lib/grape/middleware/versioner/header.rb +127 -0
- data/lib/grape/middleware/versioner/param.rb +32 -0
- data/lib/grape/middleware/versioner/path.rb +40 -0
- data/lib/grape/middleware/versioner.rb +21 -21
- data/lib/grape/namespace.rb +46 -0
- data/lib/grape/params_builder/base.rb +18 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +32 -0
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +14 -0
- data/lib/grape/parser/xml.rb +14 -0
- data/lib/grape/parser.rb +15 -0
- data/lib/grape/path.rb +76 -0
- data/lib/grape/presenters/presenter.rb +11 -0
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +190 -0
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +81 -0
- data/lib/grape/router/route.rb +62 -0
- data/lib/grape/router.rb +190 -0
- data/lib/grape/serve_stream/file_body.rb +36 -0
- data/lib/grape/serve_stream/sendfile_response.rb +21 -0
- data/lib/grape/serve_stream/stream_response.rb +23 -0
- data/lib/grape/types/invalid_value.rb +8 -0
- data/lib/grape/util/base_inheritable.rb +43 -0
- data/lib/grape/util/cache.rb +17 -0
- data/lib/grape/util/endpoint_configuration.rb +8 -0
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_setting.rb +100 -0
- data/lib/grape/util/inheritable_values.rb +29 -0
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -0
- data/lib/grape/util/registry.rb +27 -0
- data/lib/grape/util/reverse_stackable_values.rb +15 -0
- data/lib/grape/util/stackable_values.rb +36 -0
- data/lib/grape/util/strict_hash_configuration.rb +108 -0
- data/lib/grape/validations/attributes_doc.rb +60 -0
- data/lib/grape/validations/attributes_iterator.rb +62 -0
- data/lib/grape/validations/contract_scope.rb +34 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
- data/lib/grape/validations/params_scope.rb +538 -0
- data/lib/grape/validations/single_attribute_iterator.rb +26 -0
- data/lib/grape/validations/types/array_coercer.rb +61 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +164 -0
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +56 -0
- data/lib/grape/validations/types/dry_type_coercer.rb +66 -0
- data/lib/grape/validations/types/file.rb +31 -0
- data/lib/grape/validations/types/invalid_value.rb +17 -0
- data/lib/grape/validations/types/json.rb +71 -0
- data/lib/grape/validations/types/multiple_type_coercer.rb +57 -0
- data/lib/grape/validations/types/primitive_coercer.rb +73 -0
- data/lib/grape/validations/types/set_coercer.rb +35 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +51 -0
- data/lib/grape/validations/types.rb +213 -0
- data/lib/grape/validations/validator_factory.rb +15 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
- data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
- data/lib/grape/validations/validators/as_validator.rb +14 -0
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
- data/lib/grape/validations/validators/base.rb +88 -0
- data/lib/grape/validations/validators/coerce_validator.rb +75 -0
- data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
- data/lib/grape/validations/validators/default_validator.rb +37 -0
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
- data/lib/grape/validations/validators/except_values_validator.rb +24 -0
- data/lib/grape/validations/validators/length_validator.rb +49 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +34 -0
- data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
- data/lib/grape/validations/validators/presence_validator.rb +15 -0
- data/lib/grape/validations/validators/regexp_validator.rb +16 -0
- data/lib/grape/validations/validators/same_as_validator.rb +29 -0
- data/lib/grape/validations/validators/values_validator.rb +60 -0
- data/lib/grape/validations.rb +21 -0
- data/lib/grape/version.rb +6 -0
- data/lib/grape/xml.rb +10 -0
- data/lib/grape.rb +81 -17
- metadata +250 -192
- data/.document +0 -5
- data/.gitignore +0 -23
- data/.rspec +0 -2
- data/.rvmrc +0 -1
- data/Gemfile +0 -21
- data/Gemfile.lock +0 -58
- data/README.markdown +0 -75
- data/Rakefile +0 -70
- data/VERSION +0 -1
- data/autotest/discover.rb +0 -1
- data/lib/grape/middleware/auth/basic.rb +0 -30
- data/lib/grape/middleware/auth/oauth2.rb +0 -55
- data/lib/grape/middleware/prefixer.rb +0 -21
- data/lib/grape/middleware_stack.rb +0 -35
- data/spec/grape/api_spec.rb +0 -343
- data/spec/grape/endpoint_spec.rb +0 -104
- data/spec/grape/middleware/auth/basic_spec.rb +0 -31
- data/spec/grape/middleware/auth/oauth2_spec.rb +0 -88
- data/spec/grape/middleware/base_spec.rb +0 -63
- data/spec/grape/middleware/error_spec.rb +0 -49
- data/spec/grape/middleware/formatter_spec.rb +0 -87
- data/spec/grape/middleware/prefixer_spec.rb +0 -30
- data/spec/grape/middleware/versioner_spec.rb +0 -40
- data/spec/grape/middleware_stack_spec.rb +0 -47
- data/spec/grape_spec.rb +0 -1
- data/spec/spec_helper.rb +0 -20
data/lib/grape/json.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
en:
|
|
3
|
+
grape:
|
|
4
|
+
errors:
|
|
5
|
+
format: '%{attributes} %{message}'
|
|
6
|
+
messages:
|
|
7
|
+
all_or_none: 'provide all or none of parameters'
|
|
8
|
+
at_least_one: 'are missing, at least one parameter must be provided'
|
|
9
|
+
blank: 'is empty'
|
|
10
|
+
coerce: 'is invalid'
|
|
11
|
+
conflicting_types: 'query params contains conflicting types'
|
|
12
|
+
empty_message_body: 'empty message body supplied with %{body_format} content-type'
|
|
13
|
+
exactly_one: 'are missing, exactly one parameter must be provided'
|
|
14
|
+
except_values: 'has a value not allowed'
|
|
15
|
+
incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
|
|
16
|
+
invalid_accept_header:
|
|
17
|
+
problem: 'invalid accept header'
|
|
18
|
+
resolution: '%{message}'
|
|
19
|
+
invalid_formatter: 'cannot convert %{klass} to %{to_format}'
|
|
20
|
+
invalid_message_body:
|
|
21
|
+
problem: 'message body does not match declared format'
|
|
22
|
+
resolution: 'when specifying %{body_format} as content-type, you must pass valid %{body_format} in the request''s ''body'' '
|
|
23
|
+
invalid_parameters: 'query params contains invalid format or byte sequence'
|
|
24
|
+
invalid_response: 'Invalid response'
|
|
25
|
+
invalid_version_header:
|
|
26
|
+
problem: 'invalid version header'
|
|
27
|
+
resolution: '%{message}'
|
|
28
|
+
invalid_versioner_option:
|
|
29
|
+
problem: 'unknown :using for versioner: %{strategy}'
|
|
30
|
+
resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
|
|
31
|
+
invalid_with_option_for_represent:
|
|
32
|
+
problem: 'you must specify an entity class in the :with option'
|
|
33
|
+
resolution: 'eg: represent User, :with => Entity::User'
|
|
34
|
+
length: 'is expected to have length within %{min} and %{max}'
|
|
35
|
+
length_is: 'is expected to have length exactly equal to %{is}'
|
|
36
|
+
length_max: 'is expected to have length less than or equal to %{max}'
|
|
37
|
+
length_min: 'is expected to have length greater than or equal to %{min}'
|
|
38
|
+
missing_group_type: 'group type is required'
|
|
39
|
+
missing_mime_type:
|
|
40
|
+
problem: 'missing mime type for %{new_format}'
|
|
41
|
+
resolution: 'you can choose existing mime type from Grape::ContentTypes::CONTENT_TYPES or add your own with content_type :%{new_format}, ''application/%{new_format}'' '
|
|
42
|
+
missing_option: 'you must specify :%{option} options'
|
|
43
|
+
missing_vendor_option:
|
|
44
|
+
problem: 'missing :vendor option'
|
|
45
|
+
resolution: 'eg: version ''v1'', using: :header, vendor: ''twitter'''
|
|
46
|
+
summary: 'when version using header, you must specify :vendor option'
|
|
47
|
+
mutual_exclusion: 'are mutually exclusive'
|
|
48
|
+
presence: 'is missing'
|
|
49
|
+
regexp: 'is invalid'
|
|
50
|
+
same_as: 'is not the same as %{parameter}'
|
|
51
|
+
too_deep_parameters: 'query params are recursively nested over the specified limit (%{limit})'
|
|
52
|
+
too_many_multipart_files: 'the number of uploaded files exceeded the system''s configured limit (%{limit})'
|
|
53
|
+
unknown_auth_strategy: 'unknown auth strategy: %{strategy}'
|
|
54
|
+
unknown_options: 'unknown options: %{options}'
|
|
55
|
+
unknown_parameter: 'unknown parameter: %{param}'
|
|
56
|
+
unknown_params_builder: 'unknown params_builder: %{params_builder_type}'
|
|
57
|
+
unknown_validator: 'unknown validator: %{validator_type}'
|
|
58
|
+
unsupported_group_type: 'group type must be Array, Hash, JSON or Array[JSON]'
|
|
59
|
+
values: 'does not have a valid value'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Middleware
|
|
5
|
+
module Auth
|
|
6
|
+
class Base < Grape::Middleware::Base
|
|
7
|
+
def initialize(app, **options)
|
|
8
|
+
super
|
|
9
|
+
@auth_strategy = Grape::Middleware::Auth::Strategies[options[:type]].tap do |auth_strategy|
|
|
10
|
+
raise Grape::Exceptions::UnknownAuthStrategy.new(strategy: options[:type]) unless auth_strategy
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call!(env)
|
|
15
|
+
@env = env
|
|
16
|
+
@auth_strategy.create(app, options) do |*args|
|
|
17
|
+
context.instance_exec(*args, &options[:proc])
|
|
18
|
+
end.call(env)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Middleware
|
|
5
|
+
module Auth
|
|
6
|
+
module DSL
|
|
7
|
+
def auth(type = nil, options = {}, &block)
|
|
8
|
+
if type
|
|
9
|
+
namespace_inheritable(:auth, options.reverse_merge(type: type.to_sym, proc: block))
|
|
10
|
+
use Grape::Middleware::Auth::Base, namespace_inheritable(:auth)
|
|
11
|
+
else
|
|
12
|
+
namespace_inheritable(:auth)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Add HTTP Basic authorization to the API.
|
|
17
|
+
#
|
|
18
|
+
# @param [Hash] options A hash of options.
|
|
19
|
+
# @option options [String] :realm "API Authorization" The HTTP Basic realm.
|
|
20
|
+
def http_basic(options = {}, &block)
|
|
21
|
+
options[:realm] ||= 'API Authorization'
|
|
22
|
+
auth :http_basic, options, &block
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def http_digest(options = {}, &block)
|
|
26
|
+
options[:realm] ||= 'API Authorization'
|
|
27
|
+
|
|
28
|
+
if options[:realm].respond_to?(:values_at)
|
|
29
|
+
options[:realm][:opaque] ||= 'secret'
|
|
30
|
+
else
|
|
31
|
+
options[:opaque] ||= 'secret'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
auth :http_digest, options, &block
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Middleware
|
|
5
|
+
module Auth
|
|
6
|
+
module Strategies
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def add(label, strategy, option_fetcher = ->(_) { [] })
|
|
10
|
+
auth_strategies[label] = StrategyInfo.new(strategy, option_fetcher)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def auth_strategies
|
|
14
|
+
@auth_strategies ||= {
|
|
15
|
+
http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) { [settings[:realm]] })
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def [](label)
|
|
20
|
+
auth_strategies[label]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Middleware
|
|
5
|
+
module Auth
|
|
6
|
+
StrategyInfo = Struct.new(:auth_class, :settings_fetcher) do
|
|
7
|
+
def create(app, options, &block)
|
|
8
|
+
strategy_args = settings_fetcher.call(options)
|
|
9
|
+
|
|
10
|
+
auth_class.new(app, *strategy_args, &block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,43 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Grape
|
|
2
4
|
module Middleware
|
|
3
5
|
class Base
|
|
6
|
+
include Grape::DSL::Headers
|
|
7
|
+
|
|
4
8
|
attr_reader :app, :env, :options
|
|
5
|
-
|
|
9
|
+
|
|
6
10
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
|
7
11
|
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
|
8
|
-
def initialize(app, options
|
|
12
|
+
def initialize(app, **options)
|
|
9
13
|
@app = app
|
|
10
|
-
@options =
|
|
14
|
+
@options = merge_default_options(options)
|
|
15
|
+
@app_response = nil
|
|
11
16
|
end
|
|
12
|
-
|
|
13
|
-
def default_options; {} end
|
|
14
|
-
|
|
17
|
+
|
|
15
18
|
def call(env)
|
|
16
|
-
dup.call!(env)
|
|
19
|
+
dup.call!(env).to_a
|
|
17
20
|
end
|
|
18
|
-
|
|
21
|
+
|
|
19
22
|
def call!(env)
|
|
20
23
|
@env = env
|
|
21
24
|
before
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
begin
|
|
26
|
+
@app_response = @app.call(@env)
|
|
27
|
+
ensure
|
|
28
|
+
begin
|
|
29
|
+
after_response = after
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
warn "caught error of type #{e.class} in after callback inside #{self.class.name} : #{e.message}"
|
|
32
|
+
raise e
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
response = after_response || @app_response
|
|
37
|
+
merge_headers response
|
|
38
|
+
response
|
|
24
39
|
end
|
|
25
|
-
|
|
40
|
+
|
|
26
41
|
# @abstract
|
|
27
42
|
# Called before the application is called in the middleware lifecycle.
|
|
28
43
|
def before; end
|
|
44
|
+
|
|
29
45
|
# @abstract
|
|
30
46
|
# Called after the application is called in the middleware lifecycle.
|
|
31
|
-
# @
|
|
47
|
+
# @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
|
|
32
48
|
def after; end
|
|
33
|
-
|
|
34
|
-
def
|
|
35
|
-
Rack::Request.new(
|
|
49
|
+
|
|
50
|
+
def rack_request
|
|
51
|
+
@rack_request ||= Rack::Request.new(env)
|
|
36
52
|
end
|
|
37
|
-
|
|
53
|
+
|
|
54
|
+
def context
|
|
55
|
+
env[Grape::Env::API_ENDPOINT]
|
|
56
|
+
end
|
|
57
|
+
|
|
38
58
|
def response
|
|
39
|
-
Rack::Response
|
|
59
|
+
return @app_response if @app_response.is_a?(Rack::Response)
|
|
60
|
+
|
|
61
|
+
@app_response = Rack::Response[*@app_response]
|
|
62
|
+
end
|
|
63
|
+
|
|
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
|
+
def query_params
|
|
81
|
+
rack_request.GET
|
|
82
|
+
rescue Rack::QueryParser::ParamsTooDeepError
|
|
83
|
+
raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)
|
|
84
|
+
rescue Rack::Utils::ParameterTypeError
|
|
85
|
+
raise Grape::Exceptions::ConflictingTypes
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def merge_headers(response)
|
|
91
|
+
return unless headers.is_a?(Hash)
|
|
92
|
+
|
|
93
|
+
case response
|
|
94
|
+
when Rack::Response then response.headers.merge!(headers)
|
|
95
|
+
when Array then response[1].merge!(headers)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def content_types_indifferent_access
|
|
100
|
+
@content_types_indifferent_access ||= content_types.with_indifferent_access
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def merge_default_options(options)
|
|
104
|
+
if respond_to?(:default_options)
|
|
105
|
+
default_options.deep_merge(options)
|
|
106
|
+
elsif self.class.const_defined?(:DEFAULT_OPTIONS)
|
|
107
|
+
self.class::DEFAULT_OPTIONS.deep_merge(options)
|
|
108
|
+
else
|
|
109
|
+
options
|
|
110
|
+
end
|
|
40
111
|
end
|
|
41
112
|
end
|
|
42
113
|
end
|
|
43
|
-
end
|
|
114
|
+
end
|
|
@@ -1,21 +1,140 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
module Middleware
|
|
5
5
|
class Error < Base
|
|
6
|
+
DEFAULT_OPTIONS = {
|
|
7
|
+
default_status: 500,
|
|
8
|
+
default_message: '',
|
|
9
|
+
format: :txt,
|
|
10
|
+
rescue_all: false,
|
|
11
|
+
rescue_grape_exceptions: false,
|
|
12
|
+
rescue_subclasses: true,
|
|
13
|
+
rescue_options: {
|
|
14
|
+
backtrace: false,
|
|
15
|
+
original_exception: false
|
|
16
|
+
}.freeze
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def initialize(app, **options)
|
|
20
|
+
super
|
|
21
|
+
self.class.include(options[:helpers]) if options[:helpers]
|
|
22
|
+
end
|
|
23
|
+
|
|
6
24
|
def call!(env)
|
|
7
25
|
@env = env
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
26
|
+
error_response(catch(:error) { return @app.call(@env) })
|
|
27
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
28
|
+
run_rescue_handler(find_handler(e.class), e, @env[Grape::Env::API_ENDPOINT])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def rack_response(status, headers, message)
|
|
34
|
+
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
|
|
35
|
+
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(headers))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def format_message(message, backtrace, original_exception = nil)
|
|
39
|
+
format = env[Grape::Env::API_FORMAT] || options[:format]
|
|
40
|
+
formatter = Grape::ErrorFormatter.formatter_for(format, options[:error_formatters], options[:default_error_formatter])
|
|
41
|
+
return formatter.call(message, backtrace, options, env, original_exception) if formatter
|
|
42
|
+
|
|
43
|
+
throw :error,
|
|
44
|
+
status: 406,
|
|
45
|
+
message: "The requested format '#{format}' is not supported.",
|
|
46
|
+
backtrace: backtrace,
|
|
47
|
+
original_exception: original_exception
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def find_handler(klass)
|
|
51
|
+
rescue_handler_for_base_only_class(klass) ||
|
|
52
|
+
rescue_handler_for_class_or_its_ancestor(klass) ||
|
|
53
|
+
rescue_handler_for_grape_exception(klass) ||
|
|
54
|
+
rescue_handler_for_any_class(klass) ||
|
|
55
|
+
raise
|
|
14
56
|
end
|
|
15
|
-
|
|
57
|
+
|
|
16
58
|
def error_response(error = {})
|
|
17
|
-
|
|
59
|
+
status = error[:status] || options[:default_status]
|
|
60
|
+
env[Grape::Env::API_ENDPOINT].status(status) # error! may not have been called
|
|
61
|
+
message = error[:message] || options[:default_message]
|
|
62
|
+
headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|
|
|
63
|
+
h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
|
64
|
+
end
|
|
65
|
+
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
|
|
66
|
+
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
|
|
67
|
+
rack_response(status, headers, format_message(message, backtrace, original_exception))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def default_rescue_handler(exception)
|
|
71
|
+
error_response(message: exception.message, backtrace: exception.backtrace, original_exception: exception)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def rescue_handler_for_base_only_class(klass)
|
|
75
|
+
error, handler = options[:base_only_rescue_handlers]&.find { |err, _handler| klass == err }
|
|
76
|
+
|
|
77
|
+
return unless error
|
|
78
|
+
|
|
79
|
+
handler || method(:default_rescue_handler)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def rescue_handler_for_class_or_its_ancestor(klass)
|
|
83
|
+
error, handler = options[:rescue_handlers]&.find { |err, _handler| klass <= err }
|
|
84
|
+
|
|
85
|
+
return unless error
|
|
86
|
+
|
|
87
|
+
handler || method(:default_rescue_handler)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def rescue_handler_for_grape_exception(klass)
|
|
91
|
+
return unless klass <= Grape::Exceptions::Base
|
|
92
|
+
return method(:error_response) if klass == Grape::Exceptions::InvalidVersionHeader
|
|
93
|
+
return unless options[:rescue_grape_exceptions] || !options[:rescue_all]
|
|
94
|
+
|
|
95
|
+
options[:grape_exceptions_rescue_handler] || method(:error_response)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def rescue_handler_for_any_class(klass)
|
|
99
|
+
return unless klass <= StandardError
|
|
100
|
+
return unless options[:rescue_all] || options[:rescue_grape_exceptions]
|
|
101
|
+
|
|
102
|
+
options[:all_rescue_handler] || method(:default_rescue_handler)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def run_rescue_handler(handler, error, endpoint)
|
|
106
|
+
if handler.instance_of?(Symbol)
|
|
107
|
+
raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
|
|
108
|
+
|
|
109
|
+
handler = public_method(handler)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
response = catch(:error) do
|
|
113
|
+
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if error?(response)
|
|
117
|
+
error_response(response)
|
|
118
|
+
elsif response.is_a?(Rack::Response)
|
|
119
|
+
response
|
|
120
|
+
else
|
|
121
|
+
run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
|
|
126
|
+
env[Grape::Env::API_ENDPOINT].status(status) # not error! inside route
|
|
127
|
+
rack_response(
|
|
128
|
+
status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),
|
|
129
|
+
format_message(message, backtrace, original_exception)
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def error?(response)
|
|
134
|
+
return false unless response.is_a?(Hash)
|
|
135
|
+
|
|
136
|
+
response.key?(:message) && response.key?(:status) && response.key?(:headers)
|
|
18
137
|
end
|
|
19
138
|
end
|
|
20
139
|
end
|
|
21
|
-
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Middleware
|
|
5
|
+
# This is a simple middleware for adding before and after filters
|
|
6
|
+
# to Grape APIs. It is used like so:
|
|
7
|
+
#
|
|
8
|
+
# use Grape::Middleware::Filter, before: -> { do_something }, after: -> { do_something }
|
|
9
|
+
class Filter < Base
|
|
10
|
+
def before
|
|
11
|
+
app.instance_eval(&options[:before]) if options[:before]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def after
|
|
15
|
+
app.instance_eval(&options[:after]) if options[:after]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|