hanami-controller 2.0.0.beta1 → 2.0.0.beta4

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.
@@ -3,7 +3,6 @@
3
3
  require "rack"
4
4
  require "rack/response"
5
5
  require "hanami/utils/kernel"
6
- require "hanami/action/flash"
7
6
  require "hanami/action/halt"
8
7
  require "hanami/action/cookie_jar"
9
8
  require "hanami/action/cache/cache_control"
@@ -27,7 +26,7 @@ module Hanami
27
26
 
28
27
  # @since 2.0.0
29
28
  # @api private
30
- attr_reader :request, :action, :exposures, :format, :env, :view_options
29
+ attr_reader :request, :exposures, :format, :env, :view_options
31
30
 
32
31
  # @since 2.0.0
33
32
  # @api private
@@ -36,7 +35,7 @@ module Hanami
36
35
  # @since 2.0.0
37
36
  # @api private
38
37
  def self.build(status, env)
39
- new(action: "", configuration: nil, content_type: Mime.best_q_match(env[Action::HTTP_ACCEPT]), env: env).tap do |r| # rubocop:disable Layout/LineLength
38
+ new(config: nil, content_type: Mime.best_q_match(env[Action::HTTP_ACCEPT]), env: env).tap do |r|
40
39
  r.status = status
41
40
  r.body = Http::Status.message_for(status)
42
41
  r.set_format(Mime.format_for(r.content_type))
@@ -45,18 +44,18 @@ module Hanami
45
44
 
46
45
  # @since 2.0.0
47
46
  # @api private
48
- def initialize(request:, action:, configuration:, content_type: nil, env: {}, headers: {}, view_options: nil) # rubocop:disable Metrics/ParameterLists
47
+ def initialize(request:, config:, content_type: nil, env: {}, headers: {}, view_options: nil, sessions_enabled: false) # rubocop:disable Layout/LineLength, Metrics/ParameterLists
49
48
  super([], 200, headers.dup)
50
49
  set_header(Action::CONTENT_TYPE, content_type)
51
50
 
52
51
  @request = request
53
- @action = action
54
- @configuration = configuration
52
+ @config = config
55
53
  @charset = ::Rack::MediaType.params(content_type).fetch("charset", nil)
56
54
  @exposures = {}
57
55
  @env = env
58
56
  @view_options = view_options || DEFAULT_VIEW_OPTIONS
59
57
 
58
+ @sessions_enabled = sessions_enabled
60
59
  @sending_file = false
61
60
  end
62
61
 
@@ -103,19 +102,27 @@ module Hanami
103
102
  # @since 2.0.0
104
103
  # @api public
105
104
  def session
106
- env[Action::RACK_SESSION] ||= {}
105
+ unless @sessions_enabled
106
+ raise Hanami::Action::MissingSessionError.new("Hanami::Action::Response#session")
107
+ end
108
+
109
+ request.session
107
110
  end
108
111
 
109
112
  # @since 2.0.0
110
113
  # @api public
111
- def cookies
112
- @cookies ||= CookieJar.new(env.dup, headers, @configuration.cookies)
114
+ def flash
115
+ unless @sessions_enabled
116
+ raise Hanami::Action::MissingSessionError.new("Hanami::Action::Response#flash")
117
+ end
118
+
119
+ request.flash
113
120
  end
114
121
 
115
122
  # @since 2.0.0
116
123
  # @api public
117
- def flash
118
- @flash ||= Flash.new(session[Flash::KEY])
124
+ def cookies
125
+ @cookies ||= CookieJar.new(env.dup, headers, @config.cookies)
119
126
  end
120
127
 
121
128
  # @since 2.0.0
@@ -131,7 +138,7 @@ module Hanami
131
138
  # @api public
132
139
  def send_file(path)
133
140
  _send_file(
134
- Rack::File.new(path, @configuration.public_directory).call(env)
141
+ Rack::File.new(path, @config.public_directory).call(env)
135
142
  )
136
143
  end
137
144
 
@@ -139,7 +146,7 @@ module Hanami
139
146
  # @api public
140
147
  def unsafe_send_file(path)
141
148
  directory = if Pathname.new(path).relative?
142
- @configuration.root_directory
149
+ @config.root_directory
143
150
  else
144
151
  FILE_SYSTEM_ROOT
145
152
  end
@@ -175,15 +182,6 @@ module Hanami
175
182
  end
176
183
  end
177
184
 
178
- # @since 2.0.0
179
- # @api private
180
- def request_id
181
- env.fetch(Action::REQUEST_ID) do
182
- # FIXME: raise a meaningful error, by inviting devs to include Hanami::Action::Session
183
- raise "Can't find request ID"
184
- end
185
- end
186
-
187
185
  # @since 2.0.0
188
186
  # @api public
189
187
  def set_format(value) # rubocop:disable Naming/AccessorMethodName
@@ -18,6 +18,10 @@ module Hanami
18
18
 
19
19
  private
20
20
 
21
+ def sessions_enabled?
22
+ true
23
+ end
24
+
21
25
  # Finalize the response
22
26
  #
23
27
  # @return [void]
data/lib/hanami/action.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
+ require "dry/core"
5
+ require "dry/types"
4
6
  require "hanami/validations"
5
7
  require "hanami/action/validatable"
6
8
  rescue LoadError # rubocop:disable Lint/SuppressedException
7
9
  end
8
10
 
9
- require "hanami/utils/class_attribute"
11
+ require "dry/configurable"
10
12
  require "hanami/utils/callbacks"
11
13
  require "hanami/utils"
12
14
  require "hanami/utils/string"
@@ -14,14 +16,15 @@ require "hanami/utils/kernel"
14
16
  require "rack"
15
17
  require "rack/utils"
16
18
 
19
+ require_relative "action/config"
17
20
  require_relative "action/constants"
18
21
  require_relative "action/base_params"
19
- require_relative "action/configuration"
20
22
  require_relative "action/halt"
21
23
  require_relative "action/mime"
22
24
  require_relative "action/rack/file"
23
25
  require_relative "action/request"
24
26
  require_relative "action/response"
27
+ require_relative "action/error"
25
28
 
26
29
  module Hanami
27
30
  # An HTTP endpoint
@@ -36,7 +39,52 @@ module Hanami
36
39
  # # ...
37
40
  # end
38
41
  # end
42
+ #
43
+ # @api public
39
44
  class Action
45
+ extend Dry::Configurable(config_class: Config)
46
+
47
+ # See {Config} for individual setting accessor API docs
48
+ setting :handled_exceptions, default: {}
49
+ setting :formats, default: Config::DEFAULT_FORMATS
50
+ setting :default_request_format, constructor: -> (format) {
51
+ Utils::Kernel.Symbol(format) unless format.nil?
52
+ }
53
+ setting :default_response_format, constructor: -> (format) {
54
+ Utils::Kernel.Symbol(format) unless format.nil?
55
+ }
56
+ setting :accepted_formats, default: []
57
+ setting :default_charset
58
+ setting :default_headers, default: {}, constructor: -> (headers) { headers.compact }
59
+ setting :cookies, default: {}, constructor: -> (cookie_options) {
60
+ # Call `to_h` here to permit `ApplicationConfiguration::Cookies` object to be
61
+ # provided when application actions are configured
62
+ cookie_options.to_h.compact
63
+ }
64
+ setting :root_directory, constructor: -> (dir) {
65
+ Pathname(File.expand_path(dir || Dir.pwd)).realpath
66
+ }
67
+ setting :public_directory, default: Config::DEFAULT_PUBLIC_DIRECTORY
68
+ setting :before_callbacks, default: Utils::Callbacks::Chain.new, cloneable: true
69
+ setting :after_callbacks, default: Utils::Callbacks::Chain.new, cloneable: true
70
+
71
+ # @!scope class
72
+
73
+ # @!method config
74
+ # Returns the action's config. Use this to configure your action.
75
+ #
76
+ # @example Access inside class body
77
+ # class Show < Hanami::Action
78
+ # config.default_response_format = :json
79
+ # end
80
+ #
81
+ # @return [Config]
82
+ #
83
+ # @api public
84
+ # @since 2.0.0
85
+
86
+ # @!scope instance
87
+
40
88
  # Override Ruby's hook for modules.
41
89
  # It includes basic Hanami::Action modules to the given class.
42
90
  #
@@ -49,31 +97,13 @@ module Hanami
49
97
 
50
98
  if subclass.superclass == Action
51
99
  subclass.class_eval do
52
- include Utils::ClassAttribute
53
-
54
- class_attribute :before_callbacks
55
- self.before_callbacks = Utils::Callbacks::Chain.new
56
-
57
- class_attribute :after_callbacks
58
- self.after_callbacks = Utils::Callbacks::Chain.new
59
-
60
100
  include Validatable if defined?(Validatable)
61
101
  end
62
102
  end
63
103
 
64
- subclass.instance_variable_set "@configuration", configuration.dup
65
- end
66
-
67
- # @since 2.0.0
68
- # @api private
69
- def self.configuration
70
- @configuration ||= Configuration.new
71
- end
72
-
73
- class << self
74
- # @since 2.0.0
75
- # @api private
76
- alias_method :config, :configuration
104
+ if instance_variable_defined?(:@params_class)
105
+ subclass.instance_variable_set(:@params_class, @params_class)
106
+ end
77
107
  end
78
108
 
79
109
  # Returns the class which defines the params
@@ -87,12 +117,7 @@ module Hanami
87
117
  # @api private
88
118
  # @since 0.7.0
89
119
  def self.params_class
90
- @params_class ||= BaseParams
91
- end
92
-
93
- # FIXME: make this thread-safe
94
- def self.accepted_formats
95
- @accepted_formats ||= []
120
+ @params_class || BaseParams
96
121
  end
97
122
 
98
123
  # Placeholder implementation for params class method
@@ -167,7 +192,7 @@ module Hanami
167
192
  # # 2. set the article
168
193
  # # 3. `#handle`
169
194
  def self.append_before(...)
170
- before_callbacks.append(...)
195
+ config.before_callbacks.append(...)
171
196
  end
172
197
 
173
198
  class << self
@@ -191,7 +216,7 @@ module Hanami
191
216
  #
192
217
  # @see Hanami::Action::Callbacks::ClassMethods#append_before
193
218
  def self.append_after(...)
194
- after_callbacks.append(...)
219
+ config.after_callbacks.append(...)
195
220
  end
196
221
 
197
222
  class << self
@@ -215,7 +240,7 @@ module Hanami
215
240
  #
216
241
  # @see Hanami::Action::Callbacks::ClassMethods#prepend_after
217
242
  def self.prepend_before(...)
218
- before_callbacks.prepend(...)
243
+ config.before_callbacks.prepend(...)
219
244
  end
220
245
 
221
246
  # Define a callback for an Action.
@@ -234,7 +259,7 @@ module Hanami
234
259
  #
235
260
  # @see Hanami::Action::Callbacks::ClassMethods#prepend_before
236
261
  def self.prepend_after(...)
237
- after_callbacks.prepend(...)
262
+ config.after_callbacks.prepend(...)
238
263
  end
239
264
 
240
265
  # Restrict the access to the specified mime type symbols.
@@ -246,7 +271,7 @@ module Hanami
246
271
  #
247
272
  # @since 0.1.0
248
273
  #
249
- # @see Hanami::Action::Configuration#format
274
+ # @see Config#format
250
275
  #
251
276
  # @example
252
277
  # require "hanami/controller"
@@ -262,58 +287,28 @@ module Hanami
262
287
  # # When called with "*/*" => 200
263
288
  # # When called with "text/html" => 200
264
289
  # # When called with "application/json" => 200
265
- # # When called with "application/xml" => 406
290
+ # # When called with "application/xml" => 415
266
291
  def self.accept(*formats)
267
- @accepted_formats = *formats
268
- before :enforce_accepted_mime_types
292
+ config.accepted_formats = formats
269
293
  end
270
294
 
271
- # Returns a new action
272
- #
273
- # @overload new(**deps, ...)
274
- # @param deps [Hash] action dependencies
275
- #
276
- # @overload new(configuration:, **deps, ...)
277
- # @param configuration [Hanami::Action::Configuration] action configuration
278
- # @param deps [Hash] action dependencies
279
- #
280
- # @return [Hanami::Action] Action object
295
+ # @see Config#handle_exception
281
296
  #
282
297
  # @since 2.0.0
283
- def self.new(*args, configuration: self.configuration, **kwargs, &block)
284
- allocate.tap do |obj|
285
- obj.instance_variable_set(:@name, Name[name])
286
- obj.instance_variable_set(:@configuration, configuration.dup.finalize!)
287
- obj.instance_variable_set(:@accepted_mime_types, Mime.restrict_mime_types(configuration, accepted_formats))
288
- obj.send(:initialize, *args, **kwargs, &block)
289
- obj.freeze
290
- end
298
+ # @api public
299
+ def self.handle_exception(...)
300
+ config.handle_exception(...)
291
301
  end
292
302
 
303
+ # Returns a new action
304
+ #
293
305
  # @since 2.0.0
294
- # @api private
295
- module Name
296
- # @since 2.0.0
297
- # @api private
298
- MODULE_SEPARATOR_TRANSFORMER = [:gsub, "::", "."].freeze
299
-
300
- # @since 2.0.0
301
- # @api private
302
- def self.call(name)
303
- Utils::String.transform(name, MODULE_SEPARATOR_TRANSFORMER, :underscore) unless name.nil?
304
- end
305
-
306
- class << self
307
- # @since 2.0.0
308
- # @api private
309
- alias_method :[], :call
310
- end
306
+ # @api public
307
+ def initialize(config: self.class.config)
308
+ @config = config
309
+ freeze
311
310
  end
312
311
 
313
- # @since 2.0.0
314
- # @api private
315
- attr_reader :name
316
-
317
312
  # Implements the Rack/Hanami::Action protocol
318
313
  #
319
314
  # @since 0.1.0
@@ -324,16 +319,22 @@ module Hanami
324
319
 
325
320
  halted = catch :halt do
326
321
  params = self.class.params_class.new(env)
327
- request = build_request(env, params)
322
+ request = build_request(
323
+ env: env,
324
+ params: params,
325
+ sessions_enabled: sessions_enabled?
326
+ )
328
327
  response = build_response(
329
328
  request: request,
330
- action: name,
331
- configuration: configuration,
332
- content_type: Mime.calculate_content_type_with_charset(configuration, request, accepted_mime_types),
329
+ config: config,
330
+ content_type: Mime.calculate_content_type_with_charset(config, request, config.accepted_mime_types),
333
331
  env: env,
334
- headers: configuration.default_headers
332
+ headers: config.default_headers,
333
+ sessions_enabled: sessions_enabled?
335
334
  )
336
335
 
336
+ enforce_accepted_mime_types(request)
337
+
337
338
  _run_before_callbacks(request, response)
338
339
  handle(request, response)
339
340
  _run_after_callbacks(request, response)
@@ -344,12 +345,6 @@ module Hanami
344
345
  finish(request, response, halted)
345
346
  end
346
347
 
347
- # @since 2.0.0
348
- # @api public
349
- def initialize(**deps)
350
- @_deps = deps
351
- end
352
-
353
348
  protected
354
349
 
355
350
  # Hook for subclasses to apply behavior as part of action invocation
@@ -421,36 +416,44 @@ module Hanami
421
416
 
422
417
  # @since 2.0.0
423
418
  # @api private
424
- attr_reader :configuration
419
+ attr_reader :config
425
420
 
426
421
  # @since 2.0.0
427
422
  # @api private
428
- def accepted_mime_types
429
- @accepted_mime_types || configuration.mime_types
430
- end
423
+ def enforce_accepted_mime_types(request)
424
+ return if config.accepted_formats.empty?
431
425
 
432
- # @since 2.0.0
433
- # @api private
434
- def enforce_accepted_mime_types(req, *)
435
- Mime.accepted_mime_type?(req, accepted_mime_types, configuration) or halt 406
426
+ Mime.enforce_accept(request, config) { return halt 406 }
427
+ Mime.enforce_content_type(request, config) { return halt 415 }
436
428
  end
437
429
 
438
430
  # @since 2.0.0
439
431
  # @api private
440
432
  def exception_handler(exception)
441
- configuration.handled_exceptions.each do |exception_class, handler|
433
+ config.handled_exceptions.each do |exception_class, handler|
442
434
  return handler if exception.is_a?(exception_class)
443
435
  end
444
436
 
445
437
  nil
446
438
  end
447
439
 
440
+ # @see Session#sessions_enabled?
448
441
  # @since 2.0.0
449
442
  # @api private
450
- def build_request(env, params)
451
- Request.new(env, params)
443
+ def sessions_enabled?
444
+ false
452
445
  end
453
446
 
447
+ # Hook to be overridden by `Hanami::Extensions::Action` for integrated actions
448
+ #
449
+ # @since 2.0.0
450
+ # @api private
451
+ def build_request(**options)
452
+ Request.new(**options)
453
+ end
454
+
455
+ # Hook to be overridden by `Hanami::Extensions::Action` for integrated actions
456
+ #
454
457
  # @since 2.0.0
455
458
  # @api private
456
459
  def build_response(**options)
@@ -507,14 +510,14 @@ module Hanami
507
510
  # @since 0.1.0
508
511
  # @api private
509
512
  def _run_before_callbacks(*args)
510
- self.class.before_callbacks.run(self, *args)
513
+ config.before_callbacks.run(self, *args)
511
514
  nil
512
515
  end
513
516
 
514
517
  # @since 0.1.0
515
518
  # @api private
516
519
  def _run_after_callbacks(*args)
517
- self.class.after_callbacks.run(self, *args)
520
+ config.after_callbacks.run(self, *args)
518
521
  nil
519
522
  end
520
523
 
@@ -589,9 +592,9 @@ module Hanami
589
592
  case value
590
593
  when Symbol
591
594
  format = Utils::Kernel.Symbol(value)
592
- [format, Action::Mime.format_to_mime_type(format, configuration)]
595
+ [format, Action::Mime.format_to_mime_type(format, config)]
593
596
  when String
594
- [Action::Mime.detect_format(value, configuration), value]
597
+ [Action::Mime.detect_format(value, config), value]
595
598
  else
596
599
  raise Hanami::Controller::UnknownFormatError.new(value)
597
600
  end
@@ -614,7 +617,7 @@ module Hanami
614
617
  _empty_headers(res) if _requires_empty_headers?(res)
615
618
  _empty_body(res) if res.head?
616
619
 
617
- res.set_format(Action::Mime.detect_format(res.content_type, configuration))
620
+ res.set_format(Action::Mime.detect_format(res.content_type, config))
618
621
  res[:params] = req.params
619
622
  res[:format] = res.format
620
623
  res
@@ -5,6 +5,6 @@ module Hanami
5
5
  # Defines the version
6
6
  #
7
7
  # @since 0.1.0
8
- VERSION = "2.0.0.beta1"
8
+ VERSION = "2.0.0.beta4"
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami-controller
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta1
4
+ version: 2.0.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-20 00:00:00.000000000 Z
11
+ date: 2022-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -152,11 +152,12 @@ files:
152
152
  - lib/hanami/action/cache/conditional_get.rb
153
153
  - lib/hanami/action/cache/directives.rb
154
154
  - lib/hanami/action/cache/expires.rb
155
- - lib/hanami/action/configuration.rb
155
+ - lib/hanami/action/config.rb
156
156
  - lib/hanami/action/constants.rb
157
157
  - lib/hanami/action/cookie_jar.rb
158
158
  - lib/hanami/action/cookies.rb
159
159
  - lib/hanami/action/csrf_protection.rb
160
+ - lib/hanami/action/error.rb
160
161
  - lib/hanami/action/flash.rb
161
162
  - lib/hanami/action/halt.rb
162
163
  - lib/hanami/action/mime.rb
@@ -191,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
192
  - !ruby/object:Gem::Version
192
193
  version: 1.3.1
193
194
  requirements: []
194
- rubygems_version: 3.3.3
195
+ rubygems_version: 3.3.7
195
196
  signing_key:
196
197
  specification_version: 4
197
198
  summary: Complete, fast and testable actions for Rack and Hanami