grape 2.2.0 → 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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +41 -18
  5. data/UPGRADING.md +75 -1
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +25 -60
  8. data/lib/grape/api.rb +44 -76
  9. data/lib/grape/cookies.rb +31 -25
  10. data/lib/grape/dsl/api.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +27 -24
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/helpers.rb +1 -1
  14. data/lib/grape/dsl/inside_route.rb +17 -40
  15. data/lib/grape/dsl/parameters.rb +5 -5
  16. data/lib/grape/dsl/routing.rb +14 -13
  17. data/lib/grape/endpoint.rb +100 -106
  18. data/lib/grape/error_formatter/base.rb +51 -21
  19. data/lib/grape/error_formatter/json.rb +7 -24
  20. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  21. data/lib/grape/error_formatter/txt.rb +13 -20
  22. data/lib/grape/error_formatter/xml.rb +3 -13
  23. data/lib/grape/error_formatter.rb +4 -12
  24. data/lib/grape/exceptions/base.rb +18 -30
  25. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  26. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  27. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  28. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  29. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  30. data/lib/grape/exceptions/validation.rb +5 -4
  31. data/lib/grape/exceptions/validation_errors.rb +2 -2
  32. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  33. data/lib/grape/extensions/hash.rb +2 -1
  34. data/lib/grape/extensions/hashie/mash.rb +3 -5
  35. data/lib/grape/formatter/base.rb +16 -0
  36. data/lib/grape/formatter/json.rb +4 -6
  37. data/lib/grape/formatter/serializable_hash.rb +1 -1
  38. data/lib/grape/formatter/txt.rb +3 -5
  39. data/lib/grape/formatter/xml.rb +4 -6
  40. data/lib/grape/formatter.rb +4 -12
  41. data/lib/grape/locale/en.yml +44 -44
  42. data/lib/grape/middleware/auth/base.rb +11 -32
  43. data/lib/grape/middleware/auth/dsl.rb +23 -29
  44. data/lib/grape/middleware/base.rb +30 -11
  45. data/lib/grape/middleware/error.rb +18 -24
  46. data/lib/grape/middleware/formatter.rb +39 -73
  47. data/lib/grape/middleware/stack.rb +26 -36
  48. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -3
  49. data/lib/grape/middleware/versioner/base.rb +74 -0
  50. data/lib/grape/middleware/versioner/header.rb +4 -10
  51. data/lib/grape/middleware/versioner/param.rb +2 -5
  52. data/lib/grape/middleware/versioner/path.rb +0 -2
  53. data/lib/grape/middleware/versioner.rb +5 -3
  54. data/lib/grape/namespace.rb +1 -1
  55. data/lib/grape/params_builder/base.rb +18 -0
  56. data/lib/grape/params_builder/hash.rb +11 -0
  57. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  58. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  59. data/lib/grape/params_builder.rb +32 -0
  60. data/lib/grape/parser/base.rb +16 -0
  61. data/lib/grape/parser/json.rb +6 -8
  62. data/lib/grape/parser/xml.rb +6 -8
  63. data/lib/grape/parser.rb +5 -7
  64. data/lib/grape/path.rb +39 -56
  65. data/lib/grape/request.rb +162 -23
  66. data/lib/grape/router/base_route.rb +2 -2
  67. data/lib/grape/router/greedy_route.rb +2 -2
  68. data/lib/grape/router/pattern.rb +23 -18
  69. data/lib/grape/router/route.rb +14 -6
  70. data/lib/grape/router.rb +30 -12
  71. data/lib/grape/util/registry.rb +27 -0
  72. data/lib/grape/validations/contract_scope.rb +2 -39
  73. data/lib/grape/validations/params_scope.rb +15 -14
  74. data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
  75. data/lib/grape/validations/validator_factory.rb +2 -2
  76. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  77. data/lib/grape/validations/validators/base.rb +7 -11
  78. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  79. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  80. data/lib/grape/validations/validators/default_validator.rb +1 -1
  81. data/lib/grape/validations/validators/except_values_validator.rb +2 -2
  82. data/lib/grape/validations/validators/length_validator.rb +1 -1
  83. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  84. data/lib/grape/validations/validators/regexp_validator.rb +2 -2
  85. data/lib/grape/validations/validators/values_validator.rb +15 -57
  86. data/lib/grape/validations.rb +8 -17
  87. data/lib/grape/version.rb +1 -1
  88. data/lib/grape.rb +14 -2
  89. metadata +24 -16
  90. data/lib/grape/http/headers.rb +0 -55
  91. data/lib/grape/middleware/helpers.rb +0 -12
  92. data/lib/grape/middleware/versioner_helpers.rb +0 -75
  93. data/lib/grape/util/lazy/object.rb +0 -45
  94. data/lib/grape/validations/types/build_coercer.rb +0 -92
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class UnknownAuthStrategy < Base
6
+ def initialize(strategy:)
7
+ super(message: compose_message(:unknown_auth_strategy, strategy: strategy))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class UnknownParamsBuilder < Base
6
+ def initialize(params_builder_type)
7
+ super(message: compose_message(:unknown_params_builder, params_builder_type: params_builder_type))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,16 +2,17 @@
2
2
 
3
3
  module Grape
4
4
  module Exceptions
5
- class Validation < Grape::Exceptions::Base
5
+ class Validation < Base
6
6
  attr_accessor :params, :message_key
7
7
 
8
- def initialize(params:, message: nil, **args)
8
+ def initialize(params:, message: nil, status: nil, headers: nil)
9
9
  @params = params
10
10
  if message
11
11
  @message_key = message if message.is_a?(Symbol)
12
- args[:message] = translate_message(message)
12
+ message = translate_message(message)
13
13
  end
14
- super(**args)
14
+
15
+ super(status: status, message: message, headers: headers)
15
16
  end
16
17
 
17
18
  # Remove all the unnecessary stuff from Grape::Exceptions::Base like status
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Grape
4
4
  module Exceptions
5
- class ValidationErrors < Grape::Exceptions::Base
5
+ class ValidationErrors < Base
6
6
  ERRORS_FORMAT_KEY = 'grape.errors.format'
7
7
  DEFAULT_ERRORS_FORMAT = '%<attributes>s %<message>s'
8
8
 
@@ -10,7 +10,7 @@ module Grape
10
10
 
11
11
  attr_reader :errors
12
12
 
13
- def initialize(errors: [], headers: {}, **_options)
13
+ def initialize(errors: [], headers: {})
14
14
  @errors = errors.group_by(&:params)
15
15
  super(message: full_messages.join(', '), status: 400, headers: headers)
16
16
  end
@@ -8,11 +8,8 @@ module Grape
8
8
  extend ::ActiveSupport::Concern
9
9
 
10
10
  included do
11
- namespace_inheritable(:build_params_with, Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder)
12
- end
13
-
14
- def params_builder
15
- Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
11
+ Grape.deprecator.warn 'This concern has been deprecated. Use `build_with` with one of the following short_name (:hash, :hash_with_indifferent_access, :hashie_mash) instead.'
12
+ namespace_inheritable(:build_params_with, :hash_with_indifferent_access)
16
13
  end
17
14
 
18
15
  def build_params
@@ -7,7 +7,8 @@ module Grape
7
7
  extend ::ActiveSupport::Concern
8
8
 
9
9
  included do
10
- namespace_inheritable(:build_params_with, Grape::Extensions::Hash::ParamBuilder)
10
+ Grape.deprecator.warn 'This concern has been deprecated. Use `build_with` with one of the following short_name (:hash, :hash_with_indifferent_access, :hashie_mash) instead.'
11
+ namespace_inheritable(:build_params_with, :hash)
11
12
  end
12
13
 
13
14
  def build_params
@@ -6,12 +6,10 @@ module Grape
6
6
  module Mash
7
7
  module ParamBuilder
8
8
  extend ::ActiveSupport::Concern
9
- included do
10
- namespace_inheritable(:build_params_with, Grape::Extensions::Hashie::Mash::ParamBuilder)
11
- end
12
9
 
13
- def params_builder
14
- Grape::Extensions::Hashie::Mash::ParamBuilder
10
+ included do
11
+ Grape.deprecator.warn 'This concern has been deprecated. Use `build_with` with one of the following short_name (:hash, :hash_with_indifferent_access, :hashie_mash) instead.'
12
+ namespace_inheritable(:build_params_with, :hashie_mash)
15
13
  end
16
14
 
17
15
  def build_params
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Formatter
5
+ class Base
6
+ def self.call(_object, _env)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def self.inherited(klass)
11
+ super
12
+ Formatter.register(klass)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,13 +2,11 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Json
6
- class << self
7
- def call(object, _env)
8
- return object.to_json if object.respond_to?(:to_json)
5
+ class Json < Base
6
+ def self.call(object, _env)
7
+ return object.to_json if object.respond_to?(:to_json)
9
8
 
10
- ::Grape::Json.dump(object)
11
- end
9
+ ::Grape::Json.dump(object)
12
10
  end
13
11
  end
14
12
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module SerializableHash
5
+ class SerializableHash < Base
6
6
  class << self
7
7
  def call(object, _env)
8
8
  return object if object.is_a?(String)
@@ -2,11 +2,9 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Txt
6
- class << self
7
- def call(object, _env)
8
- object.respond_to?(:to_txt) ? object.to_txt : object.to_s
9
- end
5
+ class Txt < Base
6
+ def self.call(object, _env)
7
+ object.respond_to?(:to_txt) ? object.to_txt : object.to_s
10
8
  end
11
9
  end
12
10
  end
@@ -2,13 +2,11 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Xml
6
- class << self
7
- def call(object, _env)
8
- return object.to_xml if object.respond_to?(:to_xml)
5
+ class Xml < Base
6
+ def self.call(object, _env)
7
+ return object.to_xml if object.respond_to?(:to_xml)
9
8
 
10
- raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
11
- end
9
+ raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
12
10
  end
13
11
  end
14
12
  end
@@ -2,24 +2,16 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module_function
5
+ extend Grape::Util::Registry
6
6
 
7
- DEFAULTS = {
8
- json: Grape::Formatter::Json,
9
- jsonapi: Grape::Formatter::Json,
10
- serializable_hash: Grape::Formatter::SerializableHash,
11
- txt: Grape::Formatter::Txt,
12
- xml: Grape::Formatter::Xml
13
- }.freeze
7
+ module_function
14
8
 
15
9
  DEFAULT_LAMBDA_FORMATTER = ->(obj, _env) { obj }
16
10
 
17
11
  def formatter_for(api_format, formatters)
18
- select_formatter(formatters, api_format) || DEFAULT_LAMBDA_FORMATTER
19
- end
12
+ return formatters[api_format] if formatters&.key?(api_format)
20
13
 
21
- def select_formatter(formatters, api_format)
22
- formatters&.key?(api_format) ? formatters[api_format] : DEFAULTS[api_format]
14
+ registry[api_format] || DEFAULT_LAMBDA_FORMATTER
23
15
  end
24
16
  end
25
17
  end
@@ -1,59 +1,59 @@
1
+ ---
1
2
  en:
2
3
  grape:
3
4
  errors:
4
- format: ! '%{attributes} %{message}'
5
+ format: '%{attributes} %{message}'
5
6
  messages:
6
- coerce: 'is invalid'
7
- presence: 'is missing'
8
- regexp: 'is invalid'
7
+ all_or_none: 'provide all or none of parameters'
8
+ at_least_one: 'are missing, at least one parameter must be provided'
9
9
  blank: 'is empty'
10
- values: 'does not have a valid value'
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'
11
14
  except_values: 'has a value not allowed'
12
- same_as: 'is not the same as %{parameter}'
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'
13
34
  length: 'is expected to have length within %{min} and %{max}'
14
35
  length_is: 'is expected to have length exactly equal to %{is}'
15
- length_min: 'is expected to have length greater than or equal to %{min}'
16
36
  length_max: 'is expected to have length less than or equal to %{max}'
17
- missing_vendor_option:
18
- problem: 'missing :vendor option'
19
- summary: 'when version using header, you must specify :vendor option'
20
- resolution: "eg: version 'v1', using: :header, vendor: 'twitter'"
37
+ length_min: 'is expected to have length greater than or equal to %{min}'
38
+ missing_group_type: 'group type is required'
21
39
  missing_mime_type:
22
40
  problem: 'missing mime type for %{new_format}'
23
- resolution:
24
- "you can choose existing mime type from Grape::ContentTypes::CONTENT_TYPES
25
- or add your own with content_type :%{new_format}, 'application/%{new_format}'
26
- "
27
- invalid_with_option_for_represent:
28
- problem: 'you must specify an entity class in the :with option'
29
- resolution: 'eg: represent User, :with => Entity::User'
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}'' '
30
42
  missing_option: 'you must specify :%{option} options'
31
- invalid_formatter: 'cannot convert %{klass} to %{to_format}'
32
- invalid_versioner_option:
33
- problem: 'unknown :using for versioner: %{strategy}'
34
- resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
35
- unknown_validator: 'unknown validator: %{validator_type}'
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}'
36
54
  unknown_options: 'unknown options: %{options}'
37
55
  unknown_parameter: 'unknown parameter: %{param}'
38
- incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
39
- mutual_exclusion: 'are mutually exclusive'
40
- at_least_one: 'are missing, at least one parameter must be provided'
41
- exactly_one: 'are missing, exactly one parameter must be provided'
42
- all_or_none: 'provide all or none of parameters'
43
- missing_group_type: 'group type is required'
56
+ unknown_params_builder: 'unknown params_builder: %{params_builder_type}'
57
+ unknown_validator: 'unknown validator: %{validator_type}'
44
58
  unsupported_group_type: 'group type must be Array, Hash, JSON or Array[JSON]'
45
- invalid_message_body:
46
- problem: "message body does not match declared format"
47
- resolution:
48
- "when specifying %{body_format} as content-type, you must pass valid
49
- %{body_format} in the request's 'body'
50
- "
51
- empty_message_body: 'empty message body supplied with %{body_format} content-type'
52
- too_many_multipart_files: "the number of uploaded files exceeded the system's configured limit (%{limit})"
53
- invalid_accept_header:
54
- problem: 'invalid accept header'
55
- resolution: '%{message}'
56
- invalid_version_header:
57
- problem: 'invalid version header'
58
- resolution: '%{message}'
59
- invalid_response: 'Invalid response'
59
+ values: 'does not have a valid value'
@@ -3,40 +3,19 @@
3
3
  module Grape
4
4
  module Middleware
5
5
  module Auth
6
- class Base
7
- include Helpers
8
-
9
- attr_accessor :options, :app, :env
10
-
11
- def initialize(app, *options)
12
- @app = app
13
- @options = options.shift
14
- end
15
-
16
- def call(env)
17
- dup._call(env)
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
18
12
  end
19
13
 
20
- def _call(env)
21
- self.env = env
22
-
23
- if options.key?(:type)
24
- auth_proc = options[:proc]
25
- auth_proc_context = context
26
-
27
- strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
28
-
29
- throw(:error, status: 401, message: 'API Authorization Failed.') if strategy_info.blank?
30
-
31
- strategy = strategy_info.create(@app, options) do |*args|
32
- auth_proc_context.instance_exec(*args, &auth_proc)
33
- end
34
-
35
- strategy.call(env)
36
-
37
- else
38
- app.call(env)
39
- end
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)
40
19
  end
41
20
  end
42
21
  end
@@ -4,40 +4,34 @@ module Grape
4
4
  module Middleware
5
5
  module Auth
6
6
  module DSL
7
- extend ActiveSupport::Concern
8
-
9
- module ClassMethods
10
- # Add an authentication type to the API. Currently
11
- # only `:http_basic`, `:http_digest` are supported.
12
- def auth(type = nil, options = {}, &block)
13
- if type
14
- namespace_inheritable(:auth, options.reverse_merge(type: type.to_sym, proc: block))
15
- use Grape::Middleware::Auth::Base, namespace_inheritable(:auth)
16
- else
17
- namespace_inheritable(:auth)
18
- end
19
- end
20
-
21
- # Add HTTP Basic authorization to the API.
22
- #
23
- # @param [Hash] options A hash of options.
24
- # @option options [String] :realm "API Authorization" The HTTP Basic realm.
25
- def http_basic(options = {}, &block)
26
- options[:realm] ||= 'API Authorization'
27
- auth :http_basic, options, &block
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)
28
13
  end
14
+ end
29
15
 
30
- def http_digest(options = {}, &block)
31
- options[:realm] ||= 'API Authorization'
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
32
24
 
33
- if options[:realm].respond_to?(:values_at)
34
- options[:realm][:opaque] ||= 'secret'
35
- else
36
- options[:opaque] ||= 'secret'
37
- end
25
+ def http_digest(options = {}, &block)
26
+ options[:realm] ||= 'API Authorization'
38
27
 
39
- auth :http_digest, options, &block
28
+ if options[:realm].respond_to?(:values_at)
29
+ options[:realm][:opaque] ||= 'secret'
30
+ else
31
+ options[:opaque] ||= 'secret'
40
32
  end
33
+
34
+ auth :http_digest, options, &block
41
35
  end
42
36
  end
43
37
  end
@@ -3,25 +3,18 @@
3
3
  module Grape
4
4
  module Middleware
5
5
  class Base
6
- include Helpers
7
6
  include Grape::DSL::Headers
8
7
 
9
8
  attr_reader :app, :env, :options
10
9
 
11
- TEXT_HTML = 'text/html'
12
-
13
10
  # @param [Rack Application] app The standard argument for a Rack middleware.
14
11
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
15
- def initialize(app, *options)
12
+ def initialize(app, **options)
16
13
  @app = app
17
- @options = options.any? ? default_options.deep_merge(options.shift) : default_options
14
+ @options = merge_default_options(options)
18
15
  @app_response = nil
19
16
  end
20
17
 
21
- def default_options
22
- {}
23
- end
24
-
25
18
  def call(env)
26
19
  dup.call!(env).to_a
27
20
  end
@@ -54,10 +47,18 @@ module Grape
54
47
  # @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
55
48
  def after; end
56
49
 
50
+ def rack_request
51
+ @rack_request ||= Rack::Request.new(env)
52
+ end
53
+
54
+ def context
55
+ env[Grape::Env::API_ENDPOINT]
56
+ end
57
+
57
58
  def response
58
59
  return @app_response if @app_response.is_a?(Rack::Response)
59
60
 
60
- @app_response = Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
61
+ @app_response = Rack::Response[*@app_response]
61
62
  end
62
63
 
63
64
  def content_types
@@ -73,7 +74,15 @@ module Grape
73
74
  end
74
75
 
75
76
  def content_type
76
- content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
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
77
86
  end
78
87
 
79
88
  private
@@ -90,6 +99,16 @@ module Grape
90
99
  def content_types_indifferent_access
91
100
  @content_types_indifferent_access ||= content_types.with_indifferent_access
92
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
111
+ end
93
112
  end
94
113
  end
95
114
  end
@@ -3,30 +3,22 @@
3
3
  module Grape
4
4
  module Middleware
5
5
  class Error < Base
6
- def default_options
7
- {
8
- default_status: 500, # default status returned on error
9
- default_message: '',
10
- format: :txt,
11
- helpers: nil,
12
- formatters: {},
13
- error_formatters: {},
14
- rescue_all: false, # true to rescue all exceptions
15
- rescue_grape_exceptions: false,
16
- rescue_subclasses: true, # rescue subclasses of exceptions listed
17
- rescue_options: {
18
- backtrace: false, # true to display backtrace, true to let Grape handle Grape::Exceptions
19
- original_exception: false # true to display exception
20
- },
21
- rescue_handlers: {}, # rescue handler blocks
22
- base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
23
- all_rescue_handler: nil # rescue handler block to rescue from all exceptions
24
- }
25
- end
26
-
27
- def initialize(app, *options)
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)
28
20
  super
29
- self.class.include(@options[:helpers]) if @options[:helpers]
21
+ self.class.include(options[:helpers]) if options[:helpers]
30
22
  end
31
23
 
32
24
  def call!(env)
@@ -39,7 +31,7 @@ module Grape
39
31
  private
40
32
 
41
33
  def rack_response(status, headers, message)
42
- message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == TEXT_HTML
34
+ message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
43
35
  Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(headers))
44
36
  end
45
37
 
@@ -65,6 +57,7 @@ module Grape
65
57
 
66
58
  def error_response(error = {})
67
59
  status = error[:status] || options[:default_status]
60
+ env[Grape::Env::API_ENDPOINT].status(status) # error! may not have been called
68
61
  message = error[:message] || options[:default_message]
69
62
  headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|
70
63
  h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
@@ -130,6 +123,7 @@ module Grape
130
123
  end
131
124
 
132
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
133
127
  rack_response(
134
128
  status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),
135
129
  format_message(message, backtrace, original_exception)