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.
Files changed (197) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1167 -0
  3. data/CONTRIBUTING.md +156 -0
  4. data/LICENSE +1 -1
  5. data/README.md +4192 -0
  6. data/UPGRADING.md +1697 -0
  7. data/grape.gemspec +28 -105
  8. data/grape.png +0 -0
  9. data/lib/grape/api/helpers.rb +9 -0
  10. data/lib/grape/api/instance.rb +247 -0
  11. data/lib/grape/api.rb +158 -194
  12. data/lib/grape/content_types.rb +31 -0
  13. data/lib/grape/cookies.rb +50 -0
  14. data/lib/grape/dry_types.rb +10 -0
  15. data/lib/grape/dsl/api.rb +17 -0
  16. data/lib/grape/dsl/callbacks.rb +69 -0
  17. data/lib/grape/dsl/configuration.rb +15 -0
  18. data/lib/grape/dsl/desc.rb +124 -0
  19. data/lib/grape/dsl/headers.rb +21 -0
  20. data/lib/grape/dsl/helpers.rb +108 -0
  21. data/lib/grape/dsl/inside_route.rb +454 -0
  22. data/lib/grape/dsl/logger.rb +22 -0
  23. data/lib/grape/dsl/middleware.rb +54 -0
  24. data/lib/grape/dsl/parameters.rb +266 -0
  25. data/lib/grape/dsl/request_response.rb +171 -0
  26. data/lib/grape/dsl/routing.rb +248 -0
  27. data/lib/grape/dsl/settings.rb +179 -0
  28. data/lib/grape/dsl/validations.rb +57 -0
  29. data/lib/grape/endpoint.rb +398 -68
  30. data/lib/grape/env.rb +20 -0
  31. data/lib/grape/error_formatter/base.rb +68 -0
  32. data/lib/grape/error_formatter/json.rb +28 -0
  33. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  34. data/lib/grape/error_formatter/txt.rb +21 -0
  35. data/lib/grape/error_formatter/xml.rb +11 -0
  36. data/lib/grape/error_formatter.rb +15 -0
  37. data/lib/grape/exceptions/base.rb +76 -0
  38. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  39. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  40. data/lib/grape/exceptions/incompatible_option_values.rb +11 -0
  41. data/lib/grape/exceptions/invalid_accept_header.rb +11 -0
  42. data/lib/grape/exceptions/invalid_formatter.rb +11 -0
  43. data/lib/grape/exceptions/invalid_message_body.rb +11 -0
  44. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  45. data/lib/grape/exceptions/invalid_response.rb +11 -0
  46. data/lib/grape/exceptions/invalid_version_header.rb +11 -0
  47. data/lib/grape/exceptions/invalid_versioner_option.rb +11 -0
  48. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +11 -0
  49. data/lib/grape/exceptions/method_not_allowed.rb +11 -0
  50. data/lib/grape/exceptions/missing_group_type.rb +13 -0
  51. data/lib/grape/exceptions/missing_mime_type.rb +11 -0
  52. data/lib/grape/exceptions/missing_option.rb +11 -0
  53. data/lib/grape/exceptions/missing_vendor_option.rb +11 -0
  54. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  55. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  56. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  57. data/lib/grape/exceptions/unknown_options.rb +11 -0
  58. data/lib/grape/exceptions/unknown_parameter.rb +11 -0
  59. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  60. data/lib/grape/exceptions/unknown_validator.rb +11 -0
  61. data/lib/grape/exceptions/unsupported_group_type.rb +13 -0
  62. data/lib/grape/exceptions/validation.rb +25 -0
  63. data/lib/grape/exceptions/validation_array_errors.rb +14 -0
  64. data/lib/grape/exceptions/validation_errors.rb +53 -0
  65. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +24 -0
  66. data/lib/grape/extensions/hash.rb +27 -0
  67. data/lib/grape/extensions/hashie/mash.rb +24 -0
  68. data/lib/grape/formatter/base.rb +16 -0
  69. data/lib/grape/formatter/json.rb +13 -0
  70. data/lib/grape/formatter/serializable_hash.rb +39 -0
  71. data/lib/grape/formatter/txt.rb +11 -0
  72. data/lib/grape/formatter/xml.rb +13 -0
  73. data/lib/grape/formatter.rb +17 -0
  74. data/lib/grape/json.rb +10 -0
  75. data/lib/grape/locale/en.yml +59 -0
  76. data/lib/grape/middleware/auth/base.rb +23 -0
  77. data/lib/grape/middleware/auth/dsl.rb +39 -0
  78. data/lib/grape/middleware/auth/strategies.rb +25 -0
  79. data/lib/grape/middleware/auth/strategy_info.rb +15 -0
  80. data/lib/grape/middleware/base.rb +89 -18
  81. data/lib/grape/middleware/error.rb +129 -10
  82. data/lib/grape/middleware/filter.rb +19 -0
  83. data/lib/grape/middleware/formatter.rb +124 -82
  84. data/lib/grape/middleware/globals.rb +14 -0
  85. data/lib/grape/middleware/stack.rb +109 -0
  86. data/lib/grape/middleware/versioner/accept_version_header.rb +38 -0
  87. data/lib/grape/middleware/versioner/base.rb +74 -0
  88. data/lib/grape/middleware/versioner/header.rb +127 -0
  89. data/lib/grape/middleware/versioner/param.rb +32 -0
  90. data/lib/grape/middleware/versioner/path.rb +40 -0
  91. data/lib/grape/middleware/versioner.rb +21 -21
  92. data/lib/grape/namespace.rb +46 -0
  93. data/lib/grape/params_builder/base.rb +18 -0
  94. data/lib/grape/params_builder/hash.rb +11 -0
  95. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  96. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  97. data/lib/grape/params_builder.rb +32 -0
  98. data/lib/grape/parser/base.rb +16 -0
  99. data/lib/grape/parser/json.rb +14 -0
  100. data/lib/grape/parser/xml.rb +14 -0
  101. data/lib/grape/parser.rb +15 -0
  102. data/lib/grape/path.rb +76 -0
  103. data/lib/grape/presenters/presenter.rb +11 -0
  104. data/lib/grape/railtie.rb +9 -0
  105. data/lib/grape/request.rb +190 -0
  106. data/lib/grape/router/base_route.rb +39 -0
  107. data/lib/grape/router/greedy_route.rb +20 -0
  108. data/lib/grape/router/pattern.rb +81 -0
  109. data/lib/grape/router/route.rb +62 -0
  110. data/lib/grape/router.rb +190 -0
  111. data/lib/grape/serve_stream/file_body.rb +36 -0
  112. data/lib/grape/serve_stream/sendfile_response.rb +21 -0
  113. data/lib/grape/serve_stream/stream_response.rb +23 -0
  114. data/lib/grape/types/invalid_value.rb +8 -0
  115. data/lib/grape/util/base_inheritable.rb +43 -0
  116. data/lib/grape/util/cache.rb +17 -0
  117. data/lib/grape/util/endpoint_configuration.rb +8 -0
  118. data/lib/grape/util/header.rb +13 -0
  119. data/lib/grape/util/inheritable_setting.rb +100 -0
  120. data/lib/grape/util/inheritable_values.rb +29 -0
  121. data/lib/grape/util/lazy/block.rb +29 -0
  122. data/lib/grape/util/lazy/value.rb +38 -0
  123. data/lib/grape/util/lazy/value_array.rb +21 -0
  124. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  125. data/lib/grape/util/lazy/value_hash.rb +21 -0
  126. data/lib/grape/util/media_type.rb +70 -0
  127. data/lib/grape/util/registry.rb +27 -0
  128. data/lib/grape/util/reverse_stackable_values.rb +15 -0
  129. data/lib/grape/util/stackable_values.rb +36 -0
  130. data/lib/grape/util/strict_hash_configuration.rb +108 -0
  131. data/lib/grape/validations/attributes_doc.rb +60 -0
  132. data/lib/grape/validations/attributes_iterator.rb +62 -0
  133. data/lib/grape/validations/contract_scope.rb +34 -0
  134. data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
  135. data/lib/grape/validations/params_scope.rb +538 -0
  136. data/lib/grape/validations/single_attribute_iterator.rb +26 -0
  137. data/lib/grape/validations/types/array_coercer.rb +61 -0
  138. data/lib/grape/validations/types/custom_type_coercer.rb +164 -0
  139. data/lib/grape/validations/types/custom_type_collection_coercer.rb +56 -0
  140. data/lib/grape/validations/types/dry_type_coercer.rb +66 -0
  141. data/lib/grape/validations/types/file.rb +31 -0
  142. data/lib/grape/validations/types/invalid_value.rb +17 -0
  143. data/lib/grape/validations/types/json.rb +71 -0
  144. data/lib/grape/validations/types/multiple_type_coercer.rb +57 -0
  145. data/lib/grape/validations/types/primitive_coercer.rb +73 -0
  146. data/lib/grape/validations/types/set_coercer.rb +35 -0
  147. data/lib/grape/validations/types/variant_collection_coercer.rb +51 -0
  148. data/lib/grape/validations/types.rb +213 -0
  149. data/lib/grape/validations/validator_factory.rb +15 -0
  150. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  151. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  152. data/lib/grape/validations/validators/as_validator.rb +14 -0
  153. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  154. data/lib/grape/validations/validators/base.rb +88 -0
  155. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  156. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  157. data/lib/grape/validations/validators/default_validator.rb +37 -0
  158. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  159. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  160. data/lib/grape/validations/validators/length_validator.rb +49 -0
  161. data/lib/grape/validations/validators/multiple_params_base.rb +34 -0
  162. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  163. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  164. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  165. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  166. data/lib/grape/validations/validators/values_validator.rb +60 -0
  167. data/lib/grape/validations.rb +21 -0
  168. data/lib/grape/version.rb +6 -0
  169. data/lib/grape/xml.rb +10 -0
  170. data/lib/grape.rb +81 -17
  171. metadata +250 -192
  172. data/.document +0 -5
  173. data/.gitignore +0 -23
  174. data/.rspec +0 -2
  175. data/.rvmrc +0 -1
  176. data/Gemfile +0 -21
  177. data/Gemfile.lock +0 -58
  178. data/README.markdown +0 -75
  179. data/Rakefile +0 -70
  180. data/VERSION +0 -1
  181. data/autotest/discover.rb +0 -1
  182. data/lib/grape/middleware/auth/basic.rb +0 -30
  183. data/lib/grape/middleware/auth/oauth2.rb +0 -55
  184. data/lib/grape/middleware/prefixer.rb +0 -21
  185. data/lib/grape/middleware_stack.rb +0 -35
  186. data/spec/grape/api_spec.rb +0 -343
  187. data/spec/grape/endpoint_spec.rb +0 -104
  188. data/spec/grape/middleware/auth/basic_spec.rb +0 -31
  189. data/spec/grape/middleware/auth/oauth2_spec.rb +0 -88
  190. data/spec/grape/middleware/base_spec.rb +0 -63
  191. data/spec/grape/middleware/error_spec.rb +0 -49
  192. data/spec/grape/middleware/formatter_spec.rb +0 -87
  193. data/spec/grape/middleware/prefixer_spec.rb +0 -30
  194. data/spec/grape/middleware/versioner_spec.rb +0 -40
  195. data/spec/grape/middleware_stack_spec.rb +0 -47
  196. data/spec/grape_spec.rb +0 -1
  197. data/spec/spec_helper.rb +0 -20
data/lib/grape/json.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ if defined?(::MultiJson)
5
+ Json = ::MultiJson
6
+ else
7
+ Json = ::JSON
8
+ Json::ParseError = Json::ParserError
9
+ end
10
+ end
@@ -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 = default_options.merge(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
- @app_response = @app.call(@env)
23
- after || @app_response
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
- # @returns [Response, nil] a Rack SPEC response or nil to call the application afterwards.
47
+ # @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
32
48
  def after; end
33
-
34
- def request
35
- Rack::Request.new(self.env)
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.new(@app_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
- require 'grape/middleware/base'
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
- result = catch :error do
9
- @app.call(@env)
10
- end
11
-
12
- result ||= {}
13
- result.is_a?(Hash) ? error_response(result) : result
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
- Rack::Response.new([(error[:message] || options[:default_message])], error[:status] || 403, error[:headers] || {}).finish
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