grape 1.1.0 → 1.2.5

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -43
  3. data/LICENSE +1 -1
  4. data/README.md +394 -47
  5. data/UPGRADING.md +111 -0
  6. data/grape.gemspec +3 -1
  7. data/lib/grape.rb +98 -66
  8. data/lib/grape/api.rb +136 -175
  9. data/lib/grape/api/instance.rb +280 -0
  10. data/lib/grape/config.rb +32 -0
  11. data/lib/grape/dsl/callbacks.rb +20 -0
  12. data/lib/grape/dsl/desc.rb +39 -7
  13. data/lib/grape/dsl/inside_route.rb +12 -6
  14. data/lib/grape/dsl/middleware.rb +7 -0
  15. data/lib/grape/dsl/parameters.rb +9 -4
  16. data/lib/grape/dsl/routing.rb +5 -1
  17. data/lib/grape/dsl/validations.rb +4 -3
  18. data/lib/grape/eager_load.rb +18 -0
  19. data/lib/grape/endpoint.rb +42 -26
  20. data/lib/grape/error_formatter.rb +1 -1
  21. data/lib/grape/exceptions/base.rb +9 -1
  22. data/lib/grape/exceptions/invalid_response.rb +9 -0
  23. data/lib/grape/exceptions/validation_errors.rb +4 -2
  24. data/lib/grape/formatter.rb +1 -1
  25. data/lib/grape/locale/en.yml +2 -0
  26. data/lib/grape/middleware/auth/base.rb +2 -4
  27. data/lib/grape/middleware/base.rb +2 -0
  28. data/lib/grape/middleware/error.rb +9 -4
  29. data/lib/grape/middleware/helpers.rb +10 -0
  30. data/lib/grape/middleware/stack.rb +1 -1
  31. data/lib/grape/middleware/versioner/header.rb +4 -4
  32. data/lib/grape/parser.rb +1 -1
  33. data/lib/grape/request.rb +1 -1
  34. data/lib/grape/router/attribute_translator.rb +2 -0
  35. data/lib/grape/router/route.rb +2 -2
  36. data/lib/grape/util/base_inheritable.rb +34 -0
  37. data/lib/grape/util/endpoint_configuration.rb +6 -0
  38. data/lib/grape/util/inheritable_values.rb +5 -25
  39. data/lib/grape/util/lazy_block.rb +25 -0
  40. data/lib/grape/util/lazy_value.rb +95 -0
  41. data/lib/grape/util/reverse_stackable_values.rb +7 -36
  42. data/lib/grape/util/stackable_values.rb +19 -22
  43. data/lib/grape/validations/attributes_iterator.rb +5 -3
  44. data/lib/grape/validations/multiple_attributes_iterator.rb +11 -0
  45. data/lib/grape/validations/params_scope.rb +20 -14
  46. data/lib/grape/validations/single_attribute_iterator.rb +13 -0
  47. data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
  48. data/lib/grape/validations/types/file.rb +1 -1
  49. data/lib/grape/validations/validator_factory.rb +6 -11
  50. data/lib/grape/validations/validators/all_or_none.rb +6 -13
  51. data/lib/grape/validations/validators/as.rb +2 -3
  52. data/lib/grape/validations/validators/at_least_one_of.rb +5 -13
  53. data/lib/grape/validations/validators/base.rb +11 -10
  54. data/lib/grape/validations/validators/coerce.rb +4 -0
  55. data/lib/grape/validations/validators/default.rb +1 -1
  56. data/lib/grape/validations/validators/exactly_one_of.rb +6 -23
  57. data/lib/grape/validations/validators/multiple_params_base.rb +14 -10
  58. data/lib/grape/validations/validators/mutual_exclusion.rb +6 -18
  59. data/lib/grape/validations/validators/same_as.rb +23 -0
  60. data/lib/grape/version.rb +1 -1
  61. data/spec/grape/api/defines_boolean_in_params_spec.rb +37 -0
  62. data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
  63. data/spec/grape/api_remount_spec.rb +466 -0
  64. data/spec/grape/api_spec.rb +379 -1
  65. data/spec/grape/config_spec.rb +17 -0
  66. data/spec/grape/dsl/desc_spec.rb +40 -16
  67. data/spec/grape/dsl/middleware_spec.rb +8 -0
  68. data/spec/grape/dsl/routing_spec.rb +10 -0
  69. data/spec/grape/endpoint_spec.rb +40 -4
  70. data/spec/grape/exceptions/base_spec.rb +65 -0
  71. data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
  72. data/spec/grape/exceptions/validation_errors_spec.rb +6 -4
  73. data/spec/grape/integration/rack_spec.rb +22 -6
  74. data/spec/grape/middleware/auth/dsl_spec.rb +3 -3
  75. data/spec/grape/middleware/base_spec.rb +8 -0
  76. data/spec/grape/middleware/exception_spec.rb +1 -1
  77. data/spec/grape/middleware/formatter_spec.rb +15 -5
  78. data/spec/grape/middleware/versioner/header_spec.rb +6 -0
  79. data/spec/grape/named_api_spec.rb +19 -0
  80. data/spec/grape/request_spec.rb +24 -0
  81. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +29 -0
  82. data/spec/grape/validations/params_scope_spec.rb +184 -8
  83. data/spec/grape/validations/single_attribute_iterator_spec.rb +33 -0
  84. data/spec/grape/validations/validators/all_or_none_spec.rb +138 -30
  85. data/spec/grape/validations/validators/at_least_one_of_spec.rb +173 -29
  86. data/spec/grape/validations/validators/coerce_spec.rb +10 -2
  87. data/spec/grape/validations/validators/exactly_one_of_spec.rb +202 -38
  88. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +184 -27
  89. data/spec/grape/validations/validators/same_as_spec.rb +63 -0
  90. data/spec/grape/validations_spec.rb +33 -21
  91. data/spec/spec_helper.rb +4 -1
  92. metadata +35 -23
  93. data/Appraisals +0 -32
  94. data/Dangerfile +0 -2
  95. data/Gemfile +0 -33
  96. data/Gemfile.lock +0 -231
  97. data/Guardfile +0 -10
  98. data/RELEASING.md +0 -111
  99. data/Rakefile +0 -25
  100. data/benchmark/simple.rb +0 -27
  101. data/benchmark/simple_with_type_coercer.rb +0 -22
  102. data/gemfiles/multi_json.gemfile +0 -35
  103. data/gemfiles/multi_xml.gemfile +0 -35
  104. data/gemfiles/rack_1.5.2.gemfile +0 -35
  105. data/gemfiles/rack_edge.gemfile +0 -35
  106. data/gemfiles/rails_3.gemfile +0 -36
  107. data/gemfiles/rails_4.gemfile +0 -35
  108. data/gemfiles/rails_5.gemfile +0 -35
  109. data/gemfiles/rails_edge.gemfile +0 -35
  110. data/pkg/grape-0.17.0.gem +0 -0
  111. data/pkg/grape-0.19.0.gem +0 -0
@@ -0,0 +1,280 @@
1
+ require 'grape/router'
2
+
3
+ module Grape
4
+ class API
5
+ # The API Instance class, is the engine behind Grape::API. Each class that inherits
6
+ # from this will represent a different API instance
7
+ class Instance
8
+ include Grape::DSL::API
9
+
10
+ class << self
11
+ attr_reader :instance
12
+ attr_reader :base
13
+ attr_accessor :configuration
14
+
15
+ def given(conditional_option, &block)
16
+ evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block_given?
17
+ end
18
+
19
+ def mounted(&block)
20
+ evaluate_as_instance_with_configuration(block, lazy: true)
21
+ end
22
+
23
+ def base=(grape_api)
24
+ @base = grape_api
25
+ grape_api.instances << self
26
+ end
27
+
28
+ def to_s
29
+ (base && base.to_s) || super
30
+ end
31
+
32
+ def base_instance?
33
+ self == base.base_instance
34
+ end
35
+
36
+ # A class-level lock to ensure the API is not compiled by multiple
37
+ # threads simultaneously within the same process.
38
+ LOCK = Mutex.new
39
+
40
+ # Clears all defined routes, endpoints, etc., on this API.
41
+ def reset!
42
+ reset_endpoints!
43
+ reset_routes!
44
+ reset_validations!
45
+ end
46
+
47
+ # Parses the API's definition and compiles it into an instance of
48
+ # Grape::API.
49
+ def compile
50
+ @instance ||= new
51
+ end
52
+
53
+ # Wipe the compiled API so we can recompile after changes were made.
54
+ def change!
55
+ @instance = nil
56
+ end
57
+
58
+ # This is the interface point between Rack and Grape; it accepts a request
59
+ # from Rack and ultimately returns an array of three values: the status,
60
+ # the headers, and the body. See [the rack specification]
61
+ # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
62
+ def call(env)
63
+ compile!
64
+ call!(env)
65
+ end
66
+
67
+ # A non-synchronized version of ::call.
68
+ def call!(env)
69
+ instance.call(env)
70
+ end
71
+
72
+ # (see #cascade?)
73
+ def cascade(value = nil)
74
+ if value.nil?
75
+ inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
76
+ else
77
+ namespace_inheritable(:cascade, value)
78
+ end
79
+ end
80
+
81
+ def compile!
82
+ return if instance
83
+ LOCK.synchronize { compile unless instance }
84
+ end
85
+
86
+ # see Grape::Router#recognize_path
87
+ def recognize_path(path)
88
+ compile!
89
+ instance.router.recognize_path(path)
90
+ end
91
+
92
+ protected
93
+
94
+ def prepare_routes
95
+ endpoints.map(&:routes).flatten
96
+ end
97
+
98
+ # Execute first the provided block, then each of the
99
+ # block passed in. Allows for simple 'before' setups
100
+ # of settings stack pushes.
101
+ def nest(*blocks, &block)
102
+ blocks.reject!(&:nil?)
103
+ if blocks.any?
104
+ evaluate_as_instance_with_configuration(block) if block_given?
105
+ blocks.each { |b| evaluate_as_instance_with_configuration(b) }
106
+ reset_validations!
107
+ else
108
+ instance_eval(&block)
109
+ end
110
+ end
111
+
112
+ def evaluate_as_instance_with_configuration(block, lazy: false)
113
+ lazy_block = Grape::Util::LazyBlock.new do |configuration|
114
+ value_for_configuration = configuration
115
+ if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
116
+ self.configuration = value_for_configuration.evaluate
117
+ end
118
+ response = instance_eval(&block)
119
+ self.configuration = value_for_configuration
120
+ response
121
+ end
122
+ if base_instance? && lazy
123
+ lazy_block
124
+ else
125
+ lazy_block.evaluate_from(configuration)
126
+ end
127
+ end
128
+
129
+ def inherited(subclass)
130
+ subclass.reset!
131
+ subclass.logger = logger.clone
132
+ end
133
+
134
+ def inherit_settings(other_settings)
135
+ top_level_setting.inherit_from other_settings.point_in_time_copy
136
+
137
+ # Propagate any inherited params down to our endpoints, and reset any
138
+ # compiled routes.
139
+ endpoints.each do |e|
140
+ e.inherit_settings(top_level_setting.namespace_stackable)
141
+ e.reset_routes!
142
+ end
143
+
144
+ reset_routes!
145
+ end
146
+ end
147
+
148
+ attr_reader :router
149
+
150
+ # Builds the routes from the defined endpoints, effectively compiling
151
+ # this API into a usable form.
152
+ def initialize
153
+ @router = Router.new
154
+ add_head_not_allowed_methods_and_options_methods
155
+ self.class.endpoints.each do |endpoint|
156
+ endpoint.mount_in(@router)
157
+ end
158
+
159
+ @router.compile!
160
+ @router.freeze
161
+ end
162
+
163
+ # Handle a request. See Rack documentation for what `env` is.
164
+ def call(env)
165
+ result = @router.call(env)
166
+ result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
167
+ result
168
+ end
169
+
170
+ # Some requests may return a HTTP 404 error if grape cannot find a matching
171
+ # route. In this case, Grape::Router adds a X-Cascade header to the response
172
+ # and sets it to 'pass', indicating to grape's parents they should keep
173
+ # looking for a matching route on other resources.
174
+ #
175
+ # In some applications (e.g. mounting grape on rails), one might need to trap
176
+ # errors from reaching upstream. This is effectivelly done by unsetting
177
+ # X-Cascade. Default :cascade is true.
178
+ def cascade?
179
+ return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
180
+ return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
181
+ true
182
+ end
183
+
184
+ reset!
185
+
186
+ private
187
+
188
+ # For every resource add a 'OPTIONS' route that returns an HTTP 204 response
189
+ # with a list of HTTP methods that can be called. Also add a route that
190
+ # will return an HTTP 405 response for any HTTP method that the resource
191
+ # cannot handle.
192
+ def add_head_not_allowed_methods_and_options_methods
193
+ routes_map = {}
194
+
195
+ self.class.endpoints.each do |endpoint|
196
+ routes = endpoint.routes
197
+ routes.each do |route|
198
+ # using the :any shorthand produces [nil] for route methods, substitute all manually
199
+ route_key = route.pattern.to_regexp
200
+ routes_map[route_key] ||= {}
201
+ route_settings = routes_map[route_key]
202
+ route_settings[:pattern] = route.pattern
203
+ route_settings[:requirements] = route.requirements
204
+ route_settings[:path] = route.origin
205
+ route_settings[:methods] ||= []
206
+ route_settings[:methods] << route.request_method
207
+ route_settings[:endpoint] = route.app
208
+
209
+ # using the :any shorthand produces [nil] for route methods, substitute all manually
210
+ route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
211
+ end
212
+ end
213
+
214
+ # The paths we collected are prepared (cf. Path#prepare), so they
215
+ # contain already versioning information when using path versioning.
216
+ # Disable versioning so adding a route won't prepend versioning
217
+ # informations again.
218
+ without_root_prefix do
219
+ without_versioning do
220
+ routes_map.each_value do |config|
221
+ methods = config[:methods]
222
+ allowed_methods = methods.dup
223
+
224
+ unless self.class.namespace_inheritable(:do_not_route_head)
225
+ allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
226
+ end
227
+
228
+ allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
229
+
230
+ unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
231
+ config[:endpoint].options[:options_route_enabled] = true
232
+ end
233
+
234
+ attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
235
+ generate_not_allowed_method(config[:pattern], attributes)
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ # Generate a route that returns an HTTP 405 response for a user defined
242
+ # path on methods not specified
243
+ def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
244
+ not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
245
+ not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
246
+
247
+ return if not_allowed_methods.empty?
248
+
249
+ @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
250
+ end
251
+
252
+ # Allows definition of endpoints that ignore the versioning configuration
253
+ # used by the rest of your API.
254
+ def without_versioning(&_block)
255
+ old_version = self.class.namespace_inheritable(:version)
256
+ old_version_options = self.class.namespace_inheritable(:version_options)
257
+
258
+ self.class.namespace_inheritable_to_nil(:version)
259
+ self.class.namespace_inheritable_to_nil(:version_options)
260
+
261
+ yield
262
+
263
+ self.class.namespace_inheritable(:version, old_version)
264
+ self.class.namespace_inheritable(:version_options, old_version_options)
265
+ end
266
+
267
+ # Allows definition of endpoints that ignore the root prefix used by the
268
+ # rest of your API.
269
+ def without_root_prefix(&_block)
270
+ old_prefix = self.class.namespace_inheritable(:root_prefix)
271
+
272
+ self.class.namespace_inheritable_to_nil(:root_prefix)
273
+
274
+ yield
275
+
276
+ self.class.namespace_inheritable(:root_prefix, old_prefix)
277
+ end
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,32 @@
1
+ module Grape
2
+ module Config
3
+ class Configuration
4
+ ATTRIBUTES = %i[
5
+ param_builder
6
+ ].freeze
7
+
8
+ attr_accessor(*ATTRIBUTES)
9
+
10
+ def initialize
11
+ reset
12
+ end
13
+
14
+ def reset
15
+ self.param_builder = Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
16
+ end
17
+ end
18
+
19
+ def self.extended(base)
20
+ def base.configure
21
+ block_given? ? yield(config) : config
22
+ end
23
+
24
+ def base.config
25
+ @configuration ||= Grape::Config::Configuration.new
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ Grape.extend Grape::Config
32
+ Grape.config.reset
@@ -43,6 +43,26 @@ module Grape
43
43
  def after(&block)
44
44
  namespace_stackable(:afters, block)
45
45
  end
46
+
47
+ # Allows you to specify a something that will always be executed after a call
48
+ # API call. Unlike the `after` block, this code will run even on
49
+ # unsuccesful requests.
50
+ # @example
51
+ # class ExampleAPI < Grape::API
52
+ # before do
53
+ # ApiLogger.start
54
+ # end
55
+ # finally do
56
+ # ApiLogger.close
57
+ # end
58
+ # end
59
+ #
60
+ # This will make sure that the ApiLogger is opened and close around every
61
+ # request
62
+ # @param ensured_block [Proc] The block to be executed after every api_call
63
+ def finally(&block)
64
+ namespace_stackable(:finallies, block)
65
+ end
46
66
  end
47
67
  end
48
68
  end
@@ -9,6 +9,7 @@ module Grape
9
9
  # @param options [Hash] other properties you can set to describe the
10
10
  # endpoint or namespace. Optional.
11
11
  # @option options :detail [String] additional detail about this endpoint
12
+ # @option options :summary [String] summary for this endpoint
12
13
  # @option options :params [Hash] param types and info. normally, you set
13
14
  # these via the `params` dsl method.
14
15
  # @option options :entity [Grape::Entity] the entity returned upon a
@@ -16,7 +17,16 @@ module Grape
16
17
  # @option options :http_codes [Array[Array]] possible HTTP codes this
17
18
  # endpoint may return, with their meanings, in a 2d array
18
19
  # @option options :named [String] a specific name to help find this route
20
+ # @option options :body_name [String] override the autogenerated body name param
19
21
  # @option options :headers [Hash] HTTP headers this method can accept
22
+ # @option options :hidden [Boolean] hide the endpoint or not
23
+ # @option options :deprecated [Boolean] deprecate the endpoint or not
24
+ # @option options :is_array [Boolean] response entity is array or not
25
+ # @option options :nickname [String] nickname of the endpoint
26
+ # @option options :produces [Array[String]] a list of MIME types the endpoint produce
27
+ # @option options :consumes [Array[String]] a list of MIME types the endpoint consume
28
+ # @option options :security [Array[Hash]] a list of security schemes
29
+ # @option options :tags [Array[String]] a list of tags
20
30
  # @yield a block yielding an instance context with methods mapping to
21
31
  # each of the above, except that :entity is also aliased as #success
22
32
  # and :http_codes is aliased as #failure.
@@ -39,7 +49,17 @@ module Grape
39
49
  #
40
50
  def desc(description, options = {}, &config_block)
41
51
  if block_given?
42
- config_class = desc_container
52
+ endpoint_configuration = if defined?(configuration)
53
+ # When the instance is mounted - the configuration is executed on mount time
54
+ if configuration.respond_to?(:evaluate)
55
+ configuration.evaluate
56
+ # Within `given` or `mounted blocks` the configuration is already evaluated
57
+ elsif configuration.is_a?(Hash)
58
+ configuration
59
+ end
60
+ end
61
+ endpoint_configuration ||= {}
62
+ config_class = desc_container(endpoint_configuration)
43
63
 
44
64
  config_class.configure do
45
65
  description description
@@ -59,13 +79,12 @@ module Grape
59
79
  end
60
80
 
61
81
  def description_field(field, value = nil)
82
+ description = route_setting(:description)
62
83
  if value
63
- description = route_setting(:description)
64
84
  description ||= route_setting(:description, {})
65
85
  description[field] = value
66
- else
67
- description = route_setting(:description)
68
- description[field] if description
86
+ elsif description
87
+ description[field]
69
88
  end
70
89
  end
71
90
 
@@ -75,17 +94,30 @@ module Grape
75
94
  end
76
95
 
77
96
  # Returns an object which configures itself via an instance-context DSL.
78
- def desc_container
97
+ def desc_container(endpoint_configuration)
79
98
  Module.new do
80
99
  include Grape::Util::StrictHashConfiguration.module(
100
+ :summary,
81
101
  :description,
82
102
  :detail,
83
103
  :params,
84
104
  :entity,
85
105
  :http_codes,
86
106
  :named,
87
- :headers
107
+ :body_name,
108
+ :headers,
109
+ :hidden,
110
+ :deprecated,
111
+ :is_array,
112
+ :nickname,
113
+ :produces,
114
+ :consumes,
115
+ :security,
116
+ :tags
88
117
  )
118
+ config_context.define_singleton_method(:configuration) do
119
+ endpoint_configuration
120
+ end
89
121
 
90
122
  def config_context.success(*args)
91
123
  entity(*args)
@@ -61,13 +61,13 @@ module Grape
61
61
  else
62
62
  # If it is not a Hash then it does not have children.
63
63
  # Find its value or set it to nil.
64
- has_alias = route_setting(:aliased_params) && route_setting(:aliased_params).find { |current| current[declared_param] }
65
- param_alias = has_alias[declared_param] if has_alias
64
+ has_renaming = route_setting(:renamed_params) && route_setting(:renamed_params).find { |current| current[declared_param] }
65
+ param_renaming = has_renaming[declared_param] if has_renaming
66
66
 
67
- next unless options[:include_missing] || passed_params.key?(declared_param) || (param_alias && passed_params.key?(param_alias))
67
+ next unless options[:include_missing] || passed_params.key?(declared_param) || (param_renaming && passed_params.key?(param_renaming))
68
68
 
69
- if param_alias
70
- memo[optioned_param_key(param_alias, options)] = passed_params[param_alias]
69
+ if param_renaming
70
+ memo[optioned_param_key(param_renaming, options)] = passed_params[param_renaming]
71
71
  else
72
72
  memo[optioned_param_key(declared_param, options)] = passed_params[declared_param]
73
73
  end
@@ -129,13 +129,19 @@ module Grape
129
129
  env[Grape::Env::API_VERSION]
130
130
  end
131
131
 
132
+ def configuration
133
+ options[:for].configuration.evaluate
134
+ end
135
+
132
136
  # End the request and display an error to the
133
137
  # end user with the specified message.
134
138
  #
135
139
  # @param message [String] The message to display.
136
140
  # @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
137
- def error!(message, status = nil, headers = nil)
141
+ # @param additional_headers [Hash] Addtional headers for the response.
142
+ def error!(message, status = nil, additional_headers = nil)
138
143
  self.status(status || namespace_inheritable(:default_error_status))
144
+ headers = additional_headers.present? ? header.merge(additional_headers) : header
139
145
  throw :error, message: message, status: self.status, headers: headers
140
146
  end
141
147