hanami-controller 2.0.0.alpha8 → 2.0.0.beta1

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.
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