hanami-controller 2.0.0.alpha8 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/hanami/action.rb CHANGED
@@ -1,23 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
- require 'hanami/validations'
3
- require 'hanami/action/validatable'
4
- rescue LoadError
4
+ require "hanami/validations"
5
+ require "hanami/action/validatable"
6
+ rescue LoadError # rubocop:disable Lint/SuppressedException
5
7
  end
6
8
 
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 'action/base_params'
15
- require_relative 'action/configuration'
16
- require_relative 'action/halt'
17
- require_relative 'action/mime'
18
- require_relative 'action/rack/file'
19
- require_relative 'action/request'
20
- require_relative 'action/response'
9
+ require "hanami/utils/class_attribute"
10
+ require "hanami/utils/callbacks"
11
+ require "hanami/utils"
12
+ require "hanami/utils/string"
13
+ require "hanami/utils/kernel"
14
+ require "rack"
15
+ require "rack/utils"
16
+
17
+ require_relative "action/constants"
18
+ require_relative "action/base_params"
19
+ require_relative "action/configuration"
20
+ require_relative "action/halt"
21
+ require_relative "action/mime"
22
+ require_relative "action/rack/file"
23
+ require_relative "action/request"
24
+ require_relative "action/response"
21
25
 
22
26
  module Hanami
23
27
  # An HTTP endpoint
@@ -25,143 +29,14 @@ module Hanami
25
29
  # @since 0.1.0
26
30
  #
27
31
  # @example
28
- # require 'hanami/controller'
29
- #
30
- # class Show
31
- # include Hanami::Action
32
+ # require "hanami/controller"
32
33
  #
33
- # def call(params)
34
+ # class Show < Hanami::Action
35
+ # def handle(req, res)
34
36
  # # ...
35
37
  # end
36
38
  # end
37
39
  class Action
38
- # Rack SPEC response code
39
- #
40
- # @since 1.0.0
41
- # @api private
42
- RESPONSE_CODE = 0
43
-
44
- # Rack SPEC response headers
45
- #
46
- # @since 1.0.0
47
- # @api private
48
- RESPONSE_HEADERS = 1
49
-
50
- # Rack SPEC response body
51
- #
52
- # @since 1.0.0
53
- # @api private
54
- RESPONSE_BODY = 2
55
-
56
- DEFAULT_ERROR_CODE = 500
57
-
58
- # Status codes that by RFC must not include a message body
59
- #
60
- # @since 0.3.2
61
- # @api private
62
- HTTP_STATUSES_WITHOUT_BODY = Set.new((100..199).to_a << 204 << 205 << 304).freeze
63
-
64
- # Not Found
65
- #
66
- # @since 1.0.0
67
- # @api private
68
- NOT_FOUND = 404
69
-
70
- # Entity headers allowed in blank body responses, according to
71
- # RFC 2616 - Section 10 (HTTP 1.1).
72
- #
73
- # "The response MAY include new or updated metainformation in the form
74
- # of entity-headers".
75
- #
76
- # @since 0.4.0
77
- # @api private
78
- #
79
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5
80
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html
81
- ENTITY_HEADERS = {
82
- 'Allow' => true,
83
- 'Content-Encoding' => true,
84
- 'Content-Language' => true,
85
- 'Content-Location' => true,
86
- 'Content-MD5' => true,
87
- 'Content-Range' => true,
88
- 'Expires' => true,
89
- 'Last-Modified' => true,
90
- 'extension-header' => true
91
- }.freeze
92
-
93
- # The request method
94
- #
95
- # @since 0.3.2
96
- # @api private
97
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
98
-
99
- # The Content-Length HTTP header
100
- #
101
- # @since 1.0.0
102
- # @api private
103
- CONTENT_LENGTH = 'Content-Length'.freeze
104
-
105
- # The non-standard HTTP header to pass the control over when a resource
106
- # cannot be found by the current endpoint
107
- #
108
- # @since 1.0.0
109
- # @api private
110
- X_CASCADE = 'X-Cascade'.freeze
111
-
112
- # HEAD request
113
- #
114
- # @since 0.3.2
115
- # @api private
116
- HEAD = 'HEAD'.freeze
117
-
118
- # The key that returns accepted mime types from the Rack env
119
- #
120
- # @since 0.1.0
121
- # @api private
122
- HTTP_ACCEPT = 'HTTP_ACCEPT'.freeze
123
-
124
- # The header key to set the mime type of the response
125
- #
126
- # @since 0.1.0
127
- # @api private
128
- CONTENT_TYPE = 'Content-Type'.freeze
129
-
130
- # The default mime type for an incoming HTTP request
131
- #
132
- # @since 0.1.0
133
- # @api private
134
- DEFAULT_ACCEPT = '*/*'.freeze
135
-
136
- # The default mime type that is returned in the response
137
- #
138
- # @since 0.1.0
139
- # @api private
140
- DEFAULT_CONTENT_TYPE = 'application/octet-stream'.freeze
141
-
142
- # @since 0.2.0
143
- # @api private
144
- RACK_ERRORS = 'rack.errors'.freeze
145
-
146
- # This isn't part of Rack SPEC
147
- #
148
- # Exception notifiers use <tt>rack.exception</tt> instead of
149
- # <tt>rack.errors</tt>, so we need to support it.
150
- #
151
- # @since 0.5.0
152
- # @api private
153
- #
154
- # @see Hanami::Action::Throwable::RACK_ERRORS
155
- # @see http://www.rubydoc.info/github/rack/rack/file/SPEC#The_Error_Stream
156
- # @see https://github.com/hanami/controller/issues/133
157
- RACK_EXCEPTION = 'rack.exception'.freeze
158
-
159
- # The HTTP header for redirects
160
- #
161
- # @since 0.2.0
162
- # @api private
163
- LOCATION = 'Location'.freeze
164
-
165
40
  # Override Ruby's hook for modules.
166
41
  # It includes basic Hanami::Action modules to the given class.
167
42
  #
@@ -170,6 +45,8 @@ module Hanami
170
45
  # @since 0.1.0
171
46
  # @api private
172
47
  def self.inherited(subclass)
48
+ super
49
+
173
50
  if subclass.superclass == Action
174
51
  subclass.class_eval do
175
52
  include Utils::ClassAttribute
@@ -184,14 +61,18 @@ module Hanami
184
61
  end
185
62
  end
186
63
 
187
- subclass.instance_variable_set '@configuration', configuration.dup
64
+ subclass.instance_variable_set "@configuration", configuration.dup
188
65
  end
189
66
 
67
+ # @since 2.0.0
68
+ # @api private
190
69
  def self.configuration
191
70
  @configuration ||= Configuration.new
192
71
  end
193
72
 
194
73
  class << self
74
+ # @since 2.0.0
75
+ # @api private
195
76
  alias_method :config, :configuration
196
77
  end
197
78
 
@@ -214,6 +95,19 @@ module Hanami
214
95
  @accepted_formats ||= []
215
96
  end
216
97
 
98
+ # Placeholder implementation for params class method
99
+ #
100
+ # Raises a developer friendly error to include `hanami/validations`.
101
+ #
102
+ # @raise [NoMethodError]
103
+ #
104
+ # @api private
105
+ # @since 2.0.0
106
+ def self.params(_klass = nil)
107
+ raise NoMethodError,
108
+ "To use `params`, please add 'hanami/validations' gem to your Gemfile"
109
+ end
110
+
217
111
  # Define a callback for an Action.
218
112
  # The callback will be executed **before** the action is called, in the
219
113
  # order they are added.
@@ -231,14 +125,12 @@ module Hanami
231
125
  # @see Hanami::Action::Callbacks::ClassMethods#append_after
232
126
  #
233
127
  # @example Method names (symbols)
234
- # require 'hanami/controller'
235
- #
236
- # class Show
237
- # include Hanami::Action
128
+ # require "hanami/controller"
238
129
  #
130
+ # class Show < Hanami::Action
239
131
  # before :authenticate, :set_article
240
132
  #
241
- # def call(params)
133
+ # def handle(req, res)
242
134
  # end
243
135
  #
244
136
  # private
@@ -259,15 +151,13 @@ module Hanami
259
151
  # # 3. #call
260
152
  #
261
153
  # @example Anonymous functions (Procs)
262
- # require 'hanami/controller'
263
- #
264
- # class Show
265
- # include Hanami::Action
154
+ # require "hanami/controller"
266
155
  #
156
+ # class Show < Hanami::Action
267
157
  # before { ... } # 1 do some authentication stuff
268
- # before {|params| @article = Article.find params[:id] } # 2
158
+ # before {|req, res| @article = Article.find params[:id] } # 2
269
159
  #
270
- # def call(params)
160
+ # def handle(req, res)
271
161
  # end
272
162
  # end
273
163
  #
@@ -275,9 +165,9 @@ module Hanami
275
165
  # #
276
166
  # # 1. authentication
277
167
  # # 2. set the article
278
- # # 3. #call
279
- def self.append_before(*callbacks, &blk)
280
- before_callbacks.append(*callbacks, &blk)
168
+ # # 3. `#handle`
169
+ def self.append_before(...)
170
+ before_callbacks.append(...)
281
171
  end
282
172
 
283
173
  class << self
@@ -300,8 +190,8 @@ module Hanami
300
190
  # @since 0.3.2
301
191
  #
302
192
  # @see Hanami::Action::Callbacks::ClassMethods#append_before
303
- def self.append_after(*callbacks, &blk)
304
- after_callbacks.append(*callbacks, &blk)
193
+ def self.append_after(...)
194
+ after_callbacks.append(...)
305
195
  end
306
196
 
307
197
  class << self
@@ -324,8 +214,8 @@ module Hanami
324
214
  # @since 0.3.2
325
215
  #
326
216
  # @see Hanami::Action::Callbacks::ClassMethods#prepend_after
327
- def self.prepend_before(*callbacks, &blk)
328
- before_callbacks.prepend(*callbacks, &blk)
217
+ def self.prepend_before(...)
218
+ before_callbacks.prepend(...)
329
219
  end
330
220
 
331
221
  # Define a callback for an Action.
@@ -343,8 +233,8 @@ module Hanami
343
233
  # @since 0.3.2
344
234
  #
345
235
  # @see Hanami::Action::Callbacks::ClassMethods#prepend_before
346
- def self.prepend_after(*callbacks, &blk)
347
- after_callbacks.prepend(*callbacks, &blk)
236
+ def self.prepend_after(...)
237
+ after_callbacks.prepend(...)
348
238
  end
349
239
 
350
240
  # Restrict the access to the specified mime type symbols.
@@ -356,16 +246,15 @@ module Hanami
356
246
  #
357
247
  # @since 0.1.0
358
248
  #
359
- # @see Hanami::Controller::Configuration#format
249
+ # @see Hanami::Action::Configuration#format
360
250
  #
361
251
  # @example
362
- # require 'hanami/controller'
252
+ # require "hanami/controller"
363
253
  #
364
- # class Show
365
- # include Hanami::Action
254
+ # class Show < Hanami::Action
366
255
  # accept :html, :json
367
256
  #
368
- # def call(params)
257
+ # def handle(req, res)
369
258
  # # ...
370
259
  # end
371
260
  # end
@@ -385,7 +274,7 @@ module Hanami
385
274
  # @param deps [Hash] action dependencies
386
275
  #
387
276
  # @overload new(configuration:, **deps, ...)
388
- # @param configuration [Hanami::Controller::Configuration] action configuration
277
+ # @param configuration [Hanami::Action::Configuration] action configuration
389
278
  # @param deps [Hash] action dependencies
390
279
  #
391
280
  # @return [Hanami::Action] Action object
@@ -401,18 +290,28 @@ module Hanami
401
290
  end
402
291
  end
403
292
 
293
+ # @since 2.0.0
294
+ # @api private
404
295
  module Name
296
+ # @since 2.0.0
297
+ # @api private
405
298
  MODULE_SEPARATOR_TRANSFORMER = [:gsub, "::", "."].freeze
406
299
 
300
+ # @since 2.0.0
301
+ # @api private
407
302
  def self.call(name)
408
303
  Utils::String.transform(name, MODULE_SEPARATOR_TRANSFORMER, :underscore) unless name.nil?
409
304
  end
410
305
 
411
306
  class << self
307
+ # @since 2.0.0
308
+ # @api private
412
309
  alias_method :[], :call
413
310
  end
414
311
  end
415
312
 
313
+ # @since 2.0.0
314
+ # @api private
416
315
  attr_reader :name
417
316
 
418
317
  # Implements the Rack/Hanami::Action protocol
@@ -424,29 +323,29 @@ module Hanami
424
323
  response = nil
425
324
 
426
325
  halted = catch :halt do
427
- begin
428
- params = self.class.params_class.new(env)
429
- request = build_request(env, params)
430
- response = build_response(
431
- request: request,
432
- action: name,
433
- configuration: configuration,
434
- content_type: Mime.calculate_content_type_with_charset(configuration, request, accepted_mime_types),
435
- env: env,
436
- headers: configuration.default_headers
437
- )
438
-
439
- _run_before_callbacks(request, response)
440
- handle(request, response)
441
- _run_after_callbacks(request, response)
442
- rescue => exception
443
- _handle_exception(request, response, exception)
444
- end
326
+ params = self.class.params_class.new(env)
327
+ request = build_request(env, params)
328
+ response = build_response(
329
+ request: request,
330
+ action: name,
331
+ configuration: configuration,
332
+ content_type: Mime.calculate_content_type_with_charset(configuration, request, accepted_mime_types),
333
+ env: env,
334
+ headers: configuration.default_headers
335
+ )
336
+
337
+ _run_before_callbacks(request, response)
338
+ handle(request, response)
339
+ _run_after_callbacks(request, response)
340
+ rescue StandardError => exception
341
+ _handle_exception(request, response, exception)
445
342
  end
446
343
 
447
344
  finish(request, response, halted)
448
345
  end
449
346
 
347
+ # @since 2.0.0
348
+ # @api public
450
349
  def initialize(**deps)
451
350
  @_deps = deps
452
351
  end
@@ -459,6 +358,7 @@ module Hanami
459
358
  # @param response [Hanami::Action::Response]
460
359
  #
461
360
  # @since 2.0.0
361
+ # @api public
462
362
  def handle(request, response)
463
363
  end
464
364
 
@@ -483,10 +383,10 @@ module Hanami
483
383
  # @see Hanami::Http::Status:ALL
484
384
  #
485
385
  # @example Basic usage
486
- # require 'hanami/controller'
386
+ # require "hanami/controller"
487
387
  #
488
- # class Show
489
- # def call(params)
388
+ # class Show < Hanami::Action
389
+ # def handle(*)
490
390
  # halt 404
491
391
  # end
492
392
  # end
@@ -494,10 +394,10 @@ module Hanami
494
394
  # # => [404, {}, ["Not Found"]]
495
395
  #
496
396
  # @example Custom message
497
- # require 'hanami/controller'
397
+ # require "hanami/controller"
498
398
  #
499
- # class Show
500
- # def call(params)
399
+ # class Show < Hanami::Action
400
+ # def handle(*)
501
401
  # halt 404, "This is not the droid you're looking for."
502
402
  # end
503
403
  # end
@@ -515,34 +415,44 @@ module Hanami
515
415
 
516
416
  # @since 2.0.0
517
417
  # @api private
518
- def _requires_empty_headers?(res)
519
- _requires_no_body?(res) || res.head?
520
- end
418
+ alias_method :_requires_empty_headers?, :_requires_no_body?
521
419
 
522
420
  private
523
421
 
422
+ # @since 2.0.0
423
+ # @api private
524
424
  attr_reader :configuration
525
425
 
426
+ # @since 2.0.0
427
+ # @api private
526
428
  def accepted_mime_types
527
429
  @accepted_mime_types || configuration.mime_types
528
430
  end
529
431
 
432
+ # @since 2.0.0
433
+ # @api private
530
434
  def enforce_accepted_mime_types(req, *)
531
435
  Mime.accepted_mime_type?(req, accepted_mime_types, configuration) or halt 406
532
436
  end
533
437
 
438
+ # @since 2.0.0
439
+ # @api private
534
440
  def exception_handler(exception)
535
441
  configuration.handled_exceptions.each do |exception_class, handler|
536
- return handler if exception.kind_of?(exception_class)
442
+ return handler if exception.is_a?(exception_class)
537
443
  end
538
444
 
539
445
  nil
540
446
  end
541
447
 
448
+ # @since 2.0.0
449
+ # @api private
542
450
  def build_request(env, params)
543
451
  Request.new(env, params)
544
452
  end
545
453
 
454
+ # @since 2.0.0
455
+ # @api private
546
456
  def build_response(**options)
547
457
  Response.new(**options)
548
458
  end
@@ -628,28 +538,26 @@ module Hanami
628
538
  # @see Hanami::Action#_requires_no_body?
629
539
  #
630
540
  # @example
631
- # require 'hanami/controller'
541
+ # require "hanami/controller"
632
542
  #
633
543
  # module Books
634
- # class Destroy
635
- # include Hanami::Action
636
- #
637
- # def call(params)
544
+ # class Destroy < Hanami::Action
545
+ # def handle(*, res)
638
546
  # # ...
639
- # self.headers.merge!(
640
- # 'Last-Modified' => 'Fri, 27 Nov 2015 13:32:36 GMT',
641
- # 'X-Rate-Limit' => '4000',
642
- # 'Content-Type' => 'application/json',
643
- # 'X-No-Pass' => 'true'
547
+ # res.headers.merge!(
548
+ # "Last-Modified" => "Fri, 27 Nov 2015 13:32:36 GMT",
549
+ # "X-Rate-Limit" => "4000",
550
+ # "Content-Type" => "application/json",
551
+ # "X-No-Pass" => "true"
644
552
  # )
645
553
  #
646
- # self.status = 204
554
+ # res.status = 204
647
555
  # end
648
556
  #
649
557
  # private
650
558
  #
651
559
  # def keep_response_header?(header)
652
- # super || header == 'X-Rate-Limit'
560
+ # super || header == "X-Rate-Limit"
653
561
  # end
654
562
  # end
655
563
  # end
@@ -669,6 +577,14 @@ module Hanami
669
577
  res.headers.select! { |header, _| keep_response_header?(header) }
670
578
  end
671
579
 
580
+ # @since 2.0.0
581
+ # @api private
582
+ def _empty_body(res)
583
+ res.body = Response::EMPTY_BODY
584
+ end
585
+
586
+ # @since 2.0.0
587
+ # @api private
672
588
  def format(value)
673
589
  case value
674
590
  when Symbol
@@ -696,6 +612,7 @@ module Hanami
696
612
  res.status, res.body = *halted unless halted.nil?
697
613
 
698
614
  _empty_headers(res) if _requires_empty_headers?(res)
615
+ _empty_body(res) if res.head?
699
616
 
700
617
  res.set_format(Action::Mime.detect_format(res.content_type, configuration))
701
618
  res[:params] = req.params
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hanami
2
4
  module Controller
3
5
  # @since 0.5.0
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hanami
2
4
  module Controller
3
5
  # Defines the version
4
6
  #
5
7
  # @since 0.1.0
6
- VERSION = '2.0.0.alpha8'.freeze
8
+ VERSION = "2.0.0.beta1"
7
9
  end
8
10
  end
@@ -1,6 +1,8 @@
1
- require 'hanami/action'
2
- require 'hanami/controller/version'
3
- require 'hanami/controller/error'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/action"
4
+ require "hanami/controller/version"
5
+ require "hanami/controller/error"
4
6
 
5
7
  # Hanami
6
8
  #
@@ -13,18 +15,14 @@ module Hanami
13
15
  # @see Hanami::Action
14
16
  #
15
17
  # @example
16
- # require 'hanami/controller'
18
+ # require "hanami/controller"
17
19
  #
18
20
  # module Articles
19
- # class Index
20
- # include Hanami::Action
21
- #
21
+ # class Index < Hanami::Action
22
22
  # # ...
23
23
  # end
24
24
  #
25
- # class Show
26
- # include Hanami::Action
27
- #
25
+ # class Show < Hanami::Action
28
26
  # # ...
29
27
  # end
30
28
  # end
@@ -32,7 +30,7 @@ module Hanami
32
30
  # Unknown format error
33
31
  #
34
32
  # This error is raised when a action sets a format that it isn't recognized
35
- # both by `Hanami::Controller::Configuration` and the list of Rack mime types
33
+ # both by `Hanami::Action::Configuration` and the list of Rack mime types
36
34
  #
37
35
  # @since 0.2.0
38
36
  #
@@ -41,7 +39,7 @@ module Hanami
41
39
  # @since 0.2.0
42
40
  # @api private
43
41
  def initialize(format)
44
- super("Cannot find a corresponding Mime type for '#{ format }'. Please configure it with Hanami::Controller::Configuration#format.")
42
+ super("Cannot find a corresponding Mime type for '#{format}'. Please configure it with Hanami::Controller::Configuration#format.") # rubocop:disable Layout/LineLength
45
43
  end
46
44
  end
47
45
  end
@@ -1,4 +1,6 @@
1
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/utils"
2
4
 
3
5
  module Hanami
4
6
  module Http