hanami-controller 2.0.0.alpha1 → 2.0.0.alpha2

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.
@@ -7,6 +7,13 @@ module Hanami
7
7
  module Mime
8
8
  DEFAULT_CONTENT_TYPE = 'application/octet-stream'.freeze
9
9
  DEFAULT_CHARSET = 'utf-8'.freeze
10
+
11
+ # The key that returns content mime type from the Rack env
12
+ #
13
+ # @since 2.0.0
14
+ # @api private
15
+ HTTP_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
16
+
10
17
  # The header key to set the mime type of the response
11
18
  #
12
19
  # @since 0.1.0
@@ -157,7 +164,7 @@ module Hanami
157
164
  #
158
165
  # @return [TrueClass, FalseClass]
159
166
  def self.accepted_mime_type?(request, accepted_mime_types, configuration)
160
- mime_type = request.env[CONTENT_TYPE] || default_content_type(configuration) || DEFAULT_CONTENT_TYPE
167
+ mime_type = request.env[HTTP_CONTENT_TYPE] || default_content_type(configuration) || DEFAULT_CONTENT_TYPE
161
168
 
162
169
  !accepted_mime_types.find { |mt| ::Rack::Mime.match?(mt, mime_type) }.nil?
163
170
  end
@@ -126,7 +126,7 @@ module Hanami
126
126
  #
127
127
  # @since 0.7.0
128
128
  #
129
- # @see http://hanamirb.org/guides/validations/overview/
129
+ # @see https://guides.hanamirb.org/validations/overview
130
130
  #
131
131
  # @example
132
132
  # class Signup
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack'
2
4
  require 'rack/response'
3
5
  require 'hanami/utils/kernel'
@@ -11,44 +13,52 @@ require 'hanami/action/cache/conditional_get'
11
13
  module Hanami
12
14
  class Action
13
15
  class Response < ::Rack::Response
14
- REQUEST_METHOD = "REQUEST_METHOD".freeze
15
- HTTP_ACCEPT = "HTTP_ACCEPT".freeze
16
- SESSION_KEY = "rack.session".freeze
17
- REQUEST_ID = "hanami.request_id".freeze
18
- LOCATION = "Location".freeze
19
-
20
- X_CASCADE = "X-Cascade".freeze
21
- CONTENT_LENGTH = "Content-Length".freeze
16
+ DEFAULT_VIEW_OPTIONS = -> * { {} }.freeze
17
+
18
+ REQUEST_METHOD = "REQUEST_METHOD"
19
+ HTTP_ACCEPT = "HTTP_ACCEPT"
20
+ SESSION_KEY = "rack.session"
21
+ REQUEST_ID = "hanami.request_id"
22
+ LOCATION = "Location"
23
+
24
+ X_CASCADE = "X-Cascade"
25
+ CONTENT_LENGTH = "Content-Length"
22
26
  NOT_FOUND = 404
23
27
 
24
28
  RACK_STATUS = 0
25
29
  RACK_HEADERS = 1
26
30
  RACK_BODY = 2
27
31
 
28
- HEAD = "HEAD".freeze
32
+ HEAD = "HEAD"
33
+
34
+ FLASH_SESSION_KEY = "_flash"
29
35
 
30
36
  EMPTY_BODY = [].freeze
31
37
 
32
- attr_reader :action, :exposures, :format, :env
38
+ FILE_SYSTEM_ROOT = Pathname.new("/").freeze
39
+
40
+ attr_reader :request, :action, :exposures, :format, :env, :view_options
33
41
  attr_accessor :charset
34
42
 
35
- def self.build(response, env)
36
- new(action: "", configuration: nil, content_type: Mime.best_q_match(env[HTTP_ACCEPT]), env: env, header: response[RACK_HEADERS]).tap do |r|
37
- r.status = response[RACK_STATUS]
38
- r.body = response[RACK_BODY].first
43
+ def self.build(status, env)
44
+ new(action: "", configuration: nil, content_type: Mime.best_q_match(env[HTTP_ACCEPT]), env: env).tap do |r|
45
+ r.status = status
46
+ r.body = Http::Status.message_for(status)
39
47
  r.set_format(Mime.format_for(r.content_type))
40
48
  end
41
49
  end
42
50
 
43
- def initialize(action:, configuration:, content_type: nil, env: {}, header: {})
44
- super([], 200, header.dup)
51
+ def initialize(request:, action:, configuration:, content_type: nil, env: {}, headers: {}, view_options: nil)
52
+ super([], 200, headers.dup)
45
53
  set_header("Content-Type", content_type)
46
54
 
47
- @action = action
55
+ @request = request
56
+ @action = action
48
57
  @configuration = configuration
49
- @charset = ::Rack::MediaType.params(content_type).fetch('charset', nil)
58
+ @charset = ::Rack::MediaType.params(content_type).fetch('charset', nil)
50
59
  @exposures = {}
51
- @env = env
60
+ @env = env
61
+ @view_options = view_options || DEFAULT_VIEW_OPTIONS
52
62
 
53
63
  @sending_file = false
54
64
  end
@@ -65,6 +75,10 @@ module Hanami
65
75
  end
66
76
  end
67
77
 
78
+ def render(view, **options)
79
+ self.body = view.(**view_options.(request, self), **options).to_str
80
+ end
81
+
68
82
  def format=(args)
69
83
  @format, content_type = *args
70
84
  content_type = Action::Mime.content_type_with_charset(content_type, charset)
@@ -88,15 +102,12 @@ module Hanami
88
102
  end
89
103
 
90
104
  def flash
91
- @flash ||= Flash.new(session)
105
+ @flash ||= Flash.new(session[FLASH_SESSION_KEY])
92
106
  end
93
107
 
94
108
  def redirect_to(url, status: 302)
95
109
  return unless renderable?
96
110
 
97
- # This trick avoids to instantiate `flash` if it wasn't already.
98
- flash.keep! if defined?(@flash)
99
-
100
111
  redirect(::String.new(url), status)
101
112
  Halt.call(status)
102
113
  end
@@ -108,7 +119,11 @@ module Hanami
108
119
  end
109
120
 
110
121
  def unsafe_send_file(path)
111
- directory = @configuration.root_directory if Pathname.new(path).relative?
122
+ directory = if Pathname.new(path).relative?
123
+ @configuration.root_directory
124
+ else
125
+ FILE_SYSTEM_ROOT
126
+ end
112
127
 
113
128
  _send_file(
114
129
  Rack::File.new(path, directory).call(env)
@@ -148,9 +163,13 @@ module Hanami
148
163
  end
149
164
 
150
165
  def renderable?
166
+ return !head? && body.empty? if body.respond_to?(:empty?)
167
+
151
168
  !@sending_file && !head?
152
169
  end
153
170
 
171
+ alias to_ary to_a
172
+
154
173
  def head?
155
174
  env[REQUEST_METHOD] == HEAD
156
175
  end
@@ -16,17 +16,6 @@ module Hanami
16
16
 
17
17
  private
18
18
 
19
- # Container useful to transport data with the HTTP session
20
- #
21
- # @return [Hanami::Action::Flash] a Flash instance
22
- #
23
- # @since 0.3.0
24
- #
25
- # @see Hanami::Action::Flash
26
- def flash
27
- @flash ||= Flash.new(session)
28
- end
29
-
30
19
  # Finalize the response
31
20
  #
32
21
  # @return [void]
@@ -36,9 +25,12 @@ module Hanami
36
25
  #
37
26
  # @see Hanami::Action#finish
38
27
  def finish(req, res, *)
39
- res.flash.clear
40
- res[:session] = res.session
41
- res[:flash] = res.flash
28
+ if (next_flash = res.flash.next).any?
29
+ res.session['_flash'] = next_flash
30
+ else
31
+ res.session.delete('_flash')
32
+ end
33
+
42
34
  super
43
35
  end
44
36
  end
@@ -0,0 +1,581 @@
1
+ begin
2
+ require 'hanami/validations'
3
+ require 'hanami/action/validatable'
4
+ rescue LoadError
5
+ end
6
+
7
+ require 'hanami/utils/class_attribute'
8
+ require 'hanami/utils/callbacks'
9
+ require 'hanami/utils'
10
+ require 'hanami/utils/string'
11
+ require 'hanami/utils/kernel'
12
+ require 'rack/utils'
13
+
14
+ require_relative 'base_params'
15
+ require_relative 'configuration'
16
+ require_relative 'halt'
17
+ require_relative 'mime'
18
+ require_relative 'rack/file'
19
+ require_relative 'request'
20
+ require_relative 'response'
21
+
22
+ module Hanami
23
+ class Action
24
+ module StandaloneAction
25
+ def self.included(klass)
26
+ klass.include InstanceMethods
27
+ klass.extend ClassMethods
28
+ end
29
+
30
+ module ClassMethods
31
+ def configuration
32
+ @configuration ||= Configuration.new
33
+ end
34
+
35
+ alias_method :config, :configuration
36
+
37
+ # Override Ruby's hook for modules.
38
+ # It includes basic Hanami::Action modules to the given class.
39
+ #
40
+ # @param subclass [Class] the target action
41
+ #
42
+ # @since 0.1.0
43
+ # @api private
44
+ def inherited(subclass)
45
+ if subclass.superclass == Action
46
+ subclass.class_eval do
47
+ include Utils::ClassAttribute
48
+
49
+ class_attribute :before_callbacks
50
+ self.before_callbacks = Utils::Callbacks::Chain.new
51
+
52
+ class_attribute :after_callbacks
53
+ self.after_callbacks = Utils::Callbacks::Chain.new
54
+
55
+ include Validatable if defined?(Validatable)
56
+ end
57
+ end
58
+
59
+ subclass.instance_variable_set '@configuration', configuration.dup
60
+ end
61
+
62
+ # Returns the class which defines the params
63
+ #
64
+ # Returns the class which has been provided to define the
65
+ # params. By default this will be Hanami::Action::Params.
66
+ #
67
+ # @return [Class] A params class (when whitelisted) or
68
+ # Hanami::Action::Params
69
+ #
70
+ # @api private
71
+ # @since 0.7.0
72
+ def params_class
73
+ @params_class ||= BaseParams
74
+ end
75
+
76
+ # FIXME: make this thread-safe
77
+ def accepted_formats
78
+ @accepted_formats ||= []
79
+ end
80
+
81
+ # Define a callback for an Action.
82
+ # The callback will be executed **before** the action is called, in the
83
+ # order they are added.
84
+ #
85
+ # @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
86
+ # each of them is representing a name of a method available in the
87
+ # context of the Action.
88
+ #
89
+ # @param blk [Proc] an anonymous function to be executed
90
+ #
91
+ # @return [void]
92
+ #
93
+ # @since 0.3.2
94
+ #
95
+ # @see Hanami::Action::Callbacks::ClassMethods#append_after
96
+ #
97
+ # @example Method names (symbols)
98
+ # require 'hanami/controller'
99
+ #
100
+ # class Show
101
+ # include Hanami::Action
102
+ #
103
+ # before :authenticate, :set_article
104
+ #
105
+ # def call(params)
106
+ # end
107
+ #
108
+ # private
109
+ # def authenticate
110
+ # # ...
111
+ # end
112
+ #
113
+ # # `params` in the method signature is optional
114
+ # def set_article(params)
115
+ # @article = Article.find params[:id]
116
+ # end
117
+ # end
118
+ #
119
+ # # The order of execution will be:
120
+ # #
121
+ # # 1. #authenticate
122
+ # # 2. #set_article
123
+ # # 3. #call
124
+ #
125
+ # @example Anonymous functions (Procs)
126
+ # require 'hanami/controller'
127
+ #
128
+ # class Show
129
+ # include Hanami::Action
130
+ #
131
+ # before { ... } # 1 do some authentication stuff
132
+ # before {|params| @article = Article.find params[:id] } # 2
133
+ #
134
+ # def call(params)
135
+ # end
136
+ # end
137
+ #
138
+ # # The order of execution will be:
139
+ # #
140
+ # # 1. authentication
141
+ # # 2. set the article
142
+ # # 3. #call
143
+ def append_before(*callbacks, &blk)
144
+ before_callbacks.append(*callbacks, &blk)
145
+ end
146
+
147
+ alias_method :before, :append_before
148
+
149
+ # class << self
150
+ # # @since 0.1.0
151
+ # alias before append_before
152
+ # end
153
+
154
+ # Define a callback for an Action.
155
+ # The callback will be executed **after** the action is called, in the
156
+ # order they are added.
157
+ #
158
+ # @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
159
+ # each of them is representing a name of a method available in the
160
+ # context of the Action.
161
+ #
162
+ # @param blk [Proc] an anonymous function to be executed
163
+ #
164
+ # @return [void]
165
+ #
166
+ # @since 0.3.2
167
+ #
168
+ # @see Hanami::Action::Callbacks::ClassMethods#append_before
169
+ def append_after(*callbacks, &blk)
170
+ after_callbacks.append(*callbacks, &blk)
171
+ end
172
+
173
+ alias_method :after, :append_after
174
+
175
+ # class << self
176
+ # # @since 0.1.0
177
+ # alias after append_after
178
+ # end
179
+
180
+ # Define a callback for an Action.
181
+ # The callback will be executed **before** the action is called.
182
+ # It will add the callback at the beginning of the callbacks' chain.
183
+ #
184
+ # @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
185
+ # each of them is representing a name of a method available in the
186
+ # context of the Action.
187
+ #
188
+ # @param blk [Proc] an anonymous function to be executed
189
+ #
190
+ # @return [void]
191
+ #
192
+ # @since 0.3.2
193
+ #
194
+ # @see Hanami::Action::Callbacks::ClassMethods#prepend_after
195
+ def prepend_before(*callbacks, &blk)
196
+ before_callbacks.prepend(*callbacks, &blk)
197
+ end
198
+
199
+ # Define a callback for an Action.
200
+ # The callback will be executed **after** the action is called.
201
+ # It will add the callback at the beginning of the callbacks' chain.
202
+ #
203
+ # @param callbacks [Symbol, Array<Symbol>] a single or multiple symbol(s)
204
+ # each of them is representing a name of a method available in the
205
+ # context of the Action.
206
+ #
207
+ # @param blk [Proc] an anonymous function to be executed
208
+ #
209
+ # @return [void]
210
+ #
211
+ # @since 0.3.2
212
+ #
213
+ # @see Hanami::Action::Callbacks::ClassMethods#prepend_before
214
+ def prepend_after(*callbacks, &blk)
215
+ after_callbacks.prepend(*callbacks, &blk)
216
+ end
217
+
218
+ # Restrict the access to the specified mime type symbols.
219
+ #
220
+ # @param formats[Array<Symbol>] one or more symbols representing mime type(s)
221
+ #
222
+ # @raise [Hanami::Controller::UnknownFormatError] if the symbol cannot
223
+ # be converted into a mime type
224
+ #
225
+ # @since 0.1.0
226
+ #
227
+ # @see Hanami::Controller::Configuration#format
228
+ #
229
+ # @example
230
+ # require 'hanami/controller'
231
+ #
232
+ # class Show
233
+ # include Hanami::Action
234
+ # accept :html, :json
235
+ #
236
+ # def call(params)
237
+ # # ...
238
+ # end
239
+ # end
240
+ #
241
+ # # When called with "*/*" => 200
242
+ # # When called with "text/html" => 200
243
+ # # When called with "application/json" => 200
244
+ # # When called with "application/xml" => 406
245
+ def accept(*formats)
246
+ @accepted_formats = *formats
247
+ before :enforce_accepted_mime_types
248
+ end
249
+
250
+ # Returns a new action
251
+ #
252
+ # @overload new(**deps, ...)
253
+ # @param deps [Hash] action dependencies
254
+ #
255
+ # @overload new(configuration:, **deps, ...)
256
+ # @param configuration [Hanami::Controller::Configuration] action configuration
257
+ # @param deps [Hash] action dependencies
258
+ #
259
+ # @return [Hanami::Action] Action object
260
+ #
261
+ # @since 2.0.0
262
+ def new(*args, configuration: self.configuration, **kwargs, &block)
263
+ allocate.tap do |obj|
264
+ obj.instance_variable_set(:@name, Name[name])
265
+ obj.instance_variable_set(:@configuration, configuration.dup.finalize!)
266
+ obj.instance_variable_set(:@accepted_mime_types, Mime.restrict_mime_types(configuration, accepted_formats))
267
+ obj.send(:initialize, *args, **kwargs, &block)
268
+ obj.freeze
269
+ end
270
+ end
271
+
272
+ module Name
273
+ MODULE_SEPARATOR_TRANSFORMER = [:gsub, "::", "."].freeze
274
+
275
+ def self.call(name)
276
+ Utils::String.transform(name, MODULE_SEPARATOR_TRANSFORMER, :underscore) unless name.nil?
277
+ end
278
+
279
+ class << self
280
+ alias_method :[], :call
281
+ end
282
+ end
283
+ end
284
+
285
+ module InstanceMethods
286
+ attr_reader :name
287
+
288
+ # Implements the Rack/Hanami::Action protocol
289
+ #
290
+ # @since 0.1.0
291
+ # @api private
292
+ def call(env)
293
+ request = nil
294
+ response = nil
295
+
296
+ halted = catch :halt do
297
+ begin
298
+ params = self.class.params_class.new(env)
299
+ request = build_request(env, params)
300
+ response = build_response(
301
+ request: request,
302
+ action: name,
303
+ configuration: configuration,
304
+ content_type: Mime.calculate_content_type_with_charset(configuration, request, accepted_mime_types),
305
+ env: env,
306
+ headers: configuration.default_headers
307
+ )
308
+
309
+ _run_before_callbacks(request, response)
310
+ handle(request, response)
311
+ _run_after_callbacks(request, response)
312
+ rescue => exception
313
+ _handle_exception(request, response, exception)
314
+ end
315
+ end
316
+
317
+ finish(request, response, halted)
318
+ end
319
+
320
+ def initialize(**deps)
321
+ @_deps = deps
322
+ end
323
+
324
+ protected
325
+
326
+ # Hook for subclasses to apply behavior as part of action invocation
327
+ #
328
+ # @param request [Hanami::Action::Request]
329
+ # @param response [Hanami::Action::Response]
330
+ #
331
+ # @since 2.0.0
332
+ def handle(request, response)
333
+ end
334
+
335
+ # Halt the action execution with the given HTTP status code and message.
336
+ #
337
+ # When used, the execution of a callback or of an action is interrupted
338
+ # and the control returns to the framework, that decides how to handle
339
+ # the event.
340
+ #
341
+ # If a message is provided, it sets the response body with the message.
342
+ # Otherwise, it sets the response body with the default message associated
343
+ # to the code (eg 404 will set `"Not Found"`).
344
+ #
345
+ # @param status [Fixnum] a valid HTTP status code
346
+ # @param body [String] the response body
347
+ #
348
+ # @raise [StandardError] if the code isn't valid
349
+ #
350
+ # @since 0.2.0
351
+ #
352
+ # @see Hanami::Action::Throwable#handle_exception
353
+ # @see Hanami::Http::Status:ALL
354
+ #
355
+ # @example Basic usage
356
+ # require 'hanami/controller'
357
+ #
358
+ # class Show
359
+ # def call(params)
360
+ # halt 404
361
+ # end
362
+ # end
363
+ #
364
+ # # => [404, {}, ["Not Found"]]
365
+ #
366
+ # @example Custom message
367
+ # require 'hanami/controller'
368
+ #
369
+ # class Show
370
+ # def call(params)
371
+ # halt 404, "This is not the droid you're looking for."
372
+ # end
373
+ # end
374
+ #
375
+ # # => [404, {}, ["This is not the droid you're looking for."]]
376
+ def halt(status, body = nil)
377
+ Halt.call(status, body)
378
+ end
379
+
380
+ # @since 0.3.2
381
+ # @api private
382
+ def _requires_no_body?(res)
383
+ HTTP_STATUSES_WITHOUT_BODY.include?(res.status)
384
+ end
385
+
386
+ # @since 2.0.0
387
+ # @api private
388
+ def _requires_empty_headers?(res)
389
+ _requires_no_body?(res) || res.head?
390
+ end
391
+
392
+ private
393
+
394
+ attr_reader :configuration
395
+
396
+ def accepted_mime_types
397
+ @accepted_mime_types || configuration.mime_types
398
+ end
399
+
400
+ def enforce_accepted_mime_types(req, *)
401
+ Mime.accepted_mime_type?(req, accepted_mime_types, configuration) or halt 406
402
+ end
403
+
404
+ def exception_handler(exception)
405
+ configuration.handled_exceptions.each do |exception_class, handler|
406
+ return handler if exception.kind_of?(exception_class)
407
+ end
408
+
409
+ nil
410
+ end
411
+
412
+ def build_request(env, params)
413
+ Request.new(env, params)
414
+ end
415
+
416
+ def build_response(**options)
417
+ Response.new(**options)
418
+ end
419
+
420
+ # @since 0.2.0
421
+ # @api private
422
+ def _reference_in_rack_errors(req, exception)
423
+ req.env[RACK_EXCEPTION] = exception
424
+
425
+ if errors = req.env[RACK_ERRORS]
426
+ errors.write(_dump_exception(exception))
427
+ errors.flush
428
+ end
429
+ end
430
+
431
+ # @since 0.2.0
432
+ # @api private
433
+ def _dump_exception(exception)
434
+ [[exception.class, exception.message].compact.join(": "), *exception.backtrace].join("\n\t")
435
+ end
436
+
437
+ # @since 0.1.0
438
+ # @api private
439
+ def _handle_exception(req, res, exception)
440
+ handler = exception_handler(exception)
441
+
442
+ if handler.nil?
443
+ _reference_in_rack_errors(req, exception)
444
+ raise exception
445
+ end
446
+
447
+ instance_exec(
448
+ req,
449
+ res,
450
+ exception,
451
+ &_exception_handler(handler)
452
+ )
453
+
454
+ nil
455
+ end
456
+
457
+ # @since 0.3.0
458
+ # @api private
459
+ def _exception_handler(handler)
460
+ if respond_to?(handler.to_s, true)
461
+ method(handler)
462
+ else
463
+ ->(*) { halt handler }
464
+ end
465
+ end
466
+
467
+ # @since 0.1.0
468
+ # @api private
469
+ def _run_before_callbacks(*args)
470
+ self.class.before_callbacks.run(self, *args)
471
+ nil
472
+ end
473
+
474
+ # @since 0.1.0
475
+ # @api private
476
+ def _run_after_callbacks(*args)
477
+ self.class.after_callbacks.run(self, *args)
478
+ nil
479
+ end
480
+
481
+ # According to RFC 2616, when a response MUST have an empty body, it only
482
+ # allows Entity Headers.
483
+ #
484
+ # For instance, a <tt>204</tt> doesn't allow <tt>Content-Type</tt> or any
485
+ # other custom header.
486
+ #
487
+ # This restriction is enforced by <tt>Hanami::Action#_requires_no_body?</tt>.
488
+ #
489
+ # However, there are cases that demand to bypass this rule to set meta
490
+ # informations via headers.
491
+ #
492
+ # An example is a <tt>DELETE</tt> request for a JSON API application.
493
+ # It returns a <tt>204</tt> but still wants to specify the rate limit
494
+ # quota via <tt>X-Rate-Limit</tt>.
495
+ #
496
+ # @since 0.5.0
497
+ #
498
+ # @see Hanami::Action#_requires_no_body?
499
+ #
500
+ # @example
501
+ # require 'hanami/controller'
502
+ #
503
+ # module Books
504
+ # class Destroy
505
+ # include Hanami::Action
506
+ #
507
+ # def call(params)
508
+ # # ...
509
+ # self.headers.merge!(
510
+ # 'Last-Modified' => 'Fri, 27 Nov 2015 13:32:36 GMT',
511
+ # 'X-Rate-Limit' => '4000',
512
+ # 'Content-Type' => 'application/json',
513
+ # 'X-No-Pass' => 'true'
514
+ # )
515
+ #
516
+ # self.status = 204
517
+ # end
518
+ #
519
+ # private
520
+ #
521
+ # def keep_response_header?(header)
522
+ # super || header == 'X-Rate-Limit'
523
+ # end
524
+ # end
525
+ # end
526
+ #
527
+ # # Only the following headers will be sent:
528
+ # # * Last-Modified - because we used `super' in the method that respects the HTTP RFC
529
+ # # * X-Rate-Limit - because we explicitely allow it
530
+ #
531
+ # # Both Content-Type and X-No-Pass are removed because they're not allowed
532
+ def keep_response_header?(header)
533
+ ENTITY_HEADERS.include?(header)
534
+ end
535
+
536
+ # @since 2.0.0
537
+ # @api private
538
+ def _empty_headers(res)
539
+ res.headers.select! { |header, _| keep_response_header?(header) }
540
+ end
541
+
542
+ def format(value)
543
+ case value
544
+ when Symbol
545
+ format = Utils::Kernel.Symbol(value)
546
+ [format, Action::Mime.format_to_mime_type(format, configuration)]
547
+ when String
548
+ [Action::Mime.detect_format(value, configuration), value]
549
+ else
550
+ raise Hanami::Controller::UnknownFormatError.new(value)
551
+ end
552
+ end
553
+
554
+ # Finalize the response
555
+ #
556
+ # This method is abstract and COULD be implemented by included modules in
557
+ # order to prepare their data before the response will be returned to the
558
+ # webserver.
559
+ #
560
+ # @since 0.1.0
561
+ # @api private
562
+ # @abstract
563
+ #
564
+ # @see Hanami::Action::Callable#finish
565
+ # @see Hanami::Action::Session#finish
566
+ # @see Hanami::Action::Cookies#finish
567
+ # @see Hanami::Action::Cache#finish
568
+ def finish(req, res, halted)
569
+ res.status, res.body = *halted unless halted.nil?
570
+
571
+ _empty_headers(res) if _requires_empty_headers?(res)
572
+
573
+ res.set_format(Action::Mime.detect_format(res.content_type, configuration))
574
+ res[:params] = req.params
575
+ res[:format] = res.format
576
+ res
577
+ end
578
+ end
579
+ end
580
+ end
581
+ end