hanami-controller 2.0.0.beta1 → 2.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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