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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11f51222319fd677dad5bcad139e3a9fe4ec4a9099a44cab8e1809de78ca7def
4
- data.tar.gz: 38367d9e9a0831e0bae832ffc4a7528ba97ecafb399b57e085d723de522f0219
3
+ metadata.gz: a363029b7df3ddc560e2975862f72548788a2a2910c2d32519f073556b4bfbb2
4
+ data.tar.gz: e7d67505e3b0161a656d24e1ff634bd02c11d4fad1e217a1ee482271be6dbbd9
5
5
  SHA512:
6
- metadata.gz: 5522815b014bd6bf4ad6750a12c80a6db7d1aaab15562004a7898bc6cf5a5f0bbfa93d171cee31268685268ead3d37d6aa42be1bfc780c7a3bba2207814df087
7
- data.tar.gz: 379911174cd6deb4637e6dcb80731d1d221f6bb4f3c184001f6f526e8d87cdf385aa0e114a1c4fa78c2dc3361015074b5afbe428a2b630a8ff4cd17f180e5908
6
+ metadata.gz: 0a9aeb4ea44db1fe6f137dee8f3e5e05bda318a8c53f1f57c30204a0077b5dcfd82aefab569fd7169c2c728de2331fcaa52f1fcdec2e3adb6d4679c38791edf6
7
+ data.tar.gz: 726de07b87cd8055da9973ea0365c00bde1f8808a1593ef3830051e196c722f001f6dc13615182f067542c605c9437259d1d36028031867edf3abd8d9612e817
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  Complete, fast and testable actions for Rack
4
4
 
5
+ ## v2.0.0.beta4 - 2022-10-24
6
+
7
+ ### Added
8
+
9
+ - [Tim Riley] Add `Response#flash`, and delgate to request object for both `Response#session` and `Response#flash`, ensuring the same objects are used when accessed via either request or response (#399)
10
+
11
+ ### Fixed
12
+
13
+ - [Benjamin Klotz] When a params validation schema is provided (in a `params do` block), only return the validated params from `request.params` (#375)
14
+ - [Sean Collins] Handle dry-schema's messages hash now being frozen by default (#391)
15
+
16
+ ### Changed
17
+
18
+ - [Tim Riley] When `Action.accept` is declared (or `Action::Config.accepted_formats` configured), return a 406 error if an `Accept` request header is present but is not acceptable. In the absence of an `Accept` header, return a 415 error if a `Content-Type` header is present but not acceptable. If neither header is provided, accept the request. (#396)
19
+ - [Tim Riley] Add `Action.handle_exception` class method as a shortcut for `Hanami::Action::Config#handle_exception` (#394)
20
+ - [Tim Riley] Significantly reduce memory usage by leveraging recent dry-configurable changes, and relocating `accepted_formats`, `before_callbacks`, `after_callbacks` inheritable attributes to `config` (#392)
21
+ - [Tim Riley] Make params validation schemas (defined in `params do` block) inheritable to subclasses (#394)
22
+ - [Benhamin Klotz, Tim Riley] Raise `Hanami::Action::MissingSessionError` with a friendly message if `Request#session`, `Request#flash`, `Response#session` or `Response#flash` are called for an action that does not already include `Hanami::Action:Session` mixin (#379 via #395)
23
+
5
24
  ## v2.0.0.beta1 - 2022-07-20
6
25
 
7
26
  ### Fixed
data/README.md CHANGED
@@ -771,7 +771,7 @@ end
771
771
  # When called with "\*/\*" => 200
772
772
  # When called with "text/html" => 200
773
773
  # When called with "application/json" => 200
774
- # When called with "application/xml" => 406
774
+ # When called with "application/xml" => 415
775
775
  ```
776
776
 
777
777
  You can check if the requested MIME type is accepted by the client.
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require_relative "mime"
5
+
6
+ module Hanami
7
+ class Action
8
+ class Config < Dry::Configurable::Config
9
+ # Default MIME type to format mapping
10
+ #
11
+ # @since 0.2.0
12
+ # @api private
13
+ DEFAULT_FORMATS = {
14
+ "application/octet-stream" => :all,
15
+ "*/*" => :all,
16
+ "text/html" => :html
17
+ }.freeze
18
+
19
+ # Default public directory
20
+ #
21
+ # This serves as the root directory for file downloads
22
+ #
23
+ # @since 1.0.0
24
+ #
25
+ # @api private
26
+ DEFAULT_PUBLIC_DIRECTORY = "public"
27
+
28
+ # @!attribute [rw] handled_exceptions
29
+ #
30
+ # Specifies how to handle exceptions with an HTTP status.
31
+ #
32
+ # Raised exceptions will return the corresponding HTTP status.
33
+ #
34
+ # @return [Hash{Exception=>Integer}] exception classes as keys and HTTP statuses as values
35
+ #
36
+ # @example
37
+ # config.handled_exceptions = {ArgumentError => 400}
38
+ #
39
+ # @since 0.2.0
40
+
41
+ # Specifies how to handle exceptions with an HTTP status
42
+ #
43
+ # Raised exceptions will return the corresponding HTTP status
44
+ #
45
+ # The specified exceptions will be merged with any previously configured
46
+ # exceptions
47
+ #
48
+ # @param exceptions [Hash{Exception=>Integer}] exception classes as keys
49
+ # and HTTP statuses as values
50
+ #
51
+ # @return [void]
52
+ #
53
+ # @example
54
+ # config.handle_exceptions(ArgumentError => 400}
55
+ #
56
+ # @see handled_exceptions
57
+ #
58
+ # @since 0.2.0
59
+ def handle_exception(exceptions)
60
+ self.handled_exceptions = handled_exceptions
61
+ .merge(exceptions)
62
+ .sort { |(ex1, _), (ex2, _)| ex1.ancestors.include?(ex2) ? -1 : 1 }
63
+ .to_h
64
+ end
65
+
66
+ # @!attribute [rw] formats
67
+ #
68
+ # Specifies the MIME type to format mapping
69
+ #
70
+ # @return [Hash{String=>Symbol}] MIME type strings as keys and format symbols as values
71
+ #
72
+ # @see format
73
+ # @see Hanami::Action::Mime
74
+ #
75
+ # @example
76
+ # config.formats = {"text/html" => :html}
77
+ #
78
+ # @since 0.2.0
79
+
80
+ # Registers a MIME type to format mapping
81
+ #
82
+ # @param hash [Hash{Symbol=>String}] format symbols as keys and the MIME
83
+ # type strings must as values
84
+ #
85
+ # @return [void]
86
+ #
87
+ # @see formats
88
+ # @see Hanami::Action::Mime
89
+ #
90
+ # @example
91
+ # config.format html: "text/html"
92
+ #
93
+ # @since 0.2.0
94
+ def format(hash)
95
+ symbol, mime_type = *Utils::Kernel.Array(hash)
96
+ formats[Utils::Kernel.String(mime_type)] = Utils::Kernel.Symbol(symbol)
97
+ end
98
+
99
+ # Returns the configured format for the given MIME type
100
+ #
101
+ # @param mime_type [#to_s,#to_str] A mime type
102
+ #
103
+ # @return [Symbol,nil] the corresponding format, nil if not found
104
+ #
105
+ # @see format
106
+ #
107
+ # @since 0.2.0
108
+ # @api private
109
+ def format_for(mime_type)
110
+ formats[mime_type]
111
+ end
112
+
113
+ # Returns the configured format's MIME types
114
+ #
115
+ # @return [Array<String>] the format's MIME types
116
+ #
117
+ # @see formats=
118
+ # @see format
119
+ #
120
+ # @since 0.8.0
121
+ #
122
+ # @api private
123
+ def mime_types
124
+ # FIXME: this isn't efficient. speed it up!
125
+ ((formats.keys - DEFAULT_FORMATS.keys) +
126
+ Hanami::Action::Mime::TYPES.values).freeze
127
+ end
128
+
129
+ # Returns a MIME type for the given format
130
+ #
131
+ # @param format [#to_sym] a format
132
+ #
133
+ # @return [String,nil] the corresponding MIME type, if present
134
+ #
135
+ # @since 0.2.0
136
+ # @api private
137
+ def mime_type_for(format)
138
+ formats.key(format)
139
+ end
140
+
141
+ # @since 2.0.0
142
+ # @api private
143
+ def accepted_mime_types
144
+ accepted_formats.any? ? Mime.restrict_mime_types(self) : mime_types
145
+ end
146
+
147
+ # @!attribute [rw] default_request_format
148
+ #
149
+ # Sets a format as default fallback for all the requests without a strict
150
+ # requirement for the MIME type.
151
+ #
152
+ # The given format must be coercible to a symbol, and be a valid MIME
153
+ # type alias. If it isn't, at runtime the framework will raise an
154
+ # `Hanami::Controller::UnknownFormatError`.
155
+ #
156
+ # By default, this value is nil.
157
+ #
158
+ # @return [Symbol]
159
+ #
160
+ # @see Hanami::Action::Mime
161
+ #
162
+ # @since 0.5.0
163
+
164
+ # @!attribute [rw] default_response_format
165
+ #
166
+ # Sets a format to be used for all responses regardless of the request
167
+ # type.
168
+ #
169
+ # The given format must be coercible to a symbol, and be a valid MIME
170
+ # type alias. If it isn't, at the runtime the framework will raise an
171
+ # `Hanami::Controller::UnknownFormatError`.
172
+ #
173
+ # By default, this value is nil.
174
+ #
175
+ # @return [Symbol]
176
+ #
177
+ # @see Hanami::Action::Mime
178
+ #
179
+ # @since 0.5.0
180
+
181
+ # @!attribute [rw] default_charset
182
+ #
183
+ # Sets a charset (character set) as default fallback for all the requests
184
+ # without a strict requirement for the charset.
185
+ #
186
+ # By default, this value is nil.
187
+ #
188
+ # @return [String]
189
+ #
190
+ # @see Hanami::Action::Mime
191
+ #
192
+ # @since 0.3.0
193
+
194
+ # @!attribute [rw] default_headers
195
+ #
196
+ # Sets default headers for all responses.
197
+ #
198
+ # By default, this is an empty hash.
199
+ #
200
+ # @return [Hash{String=>String}] the headers
201
+ #
202
+ # @example
203
+ # config.default_headers = {"X-Frame-Options" => "DENY"}
204
+ #
205
+ # @see default_headers
206
+ #
207
+ # @since 0.4.0
208
+
209
+ # @!attribute [rw] cookies
210
+ #
211
+ # Sets default cookie options for all responses.
212
+ #
213
+ # By default this, is an empty hash.
214
+ #
215
+ # @return [Hash{Symbol=>String}] the cookie options
216
+ #
217
+ # @example
218
+ # config.cookies = {
219
+ # domain: "hanamirb.org",
220
+ # path: "/controller",
221
+ # secure: true,
222
+ # httponly: true
223
+ # }
224
+ #
225
+ # @since 0.4.0
226
+
227
+ # @!attribute [rw] root_directory
228
+ #
229
+ # Sets the the for the public directory, which is used for file downloads.
230
+ # This must be an existent directory.
231
+ #
232
+ # Defaults to the current working directory.
233
+ #
234
+ # @return [String] the directory path
235
+ #
236
+ # @api private
237
+ #
238
+ # @since 1.0.0
239
+
240
+ # @!attribute [rw] public_directory
241
+ #
242
+ # Sets the path to public directory. This directory is used for file downloads.
243
+ #
244
+ # This given directory will be appended onto the root directory.
245
+ #
246
+ # By default, the public directory is `"public"`.
247
+ # @return [String] the public directory path
248
+ #
249
+ # @example
250
+ # config.public_directory = "public"
251
+ # config.public_directory # => "/path/to/root/public"
252
+ #
253
+ # @see root_directory
254
+ #
255
+ # @since 2.0.0
256
+ def public_directory
257
+ # This must be a string, for Rack compatibility
258
+ root_directory.join(super).to_s
259
+ end
260
+ end
261
+ end
262
+ end
@@ -114,13 +114,13 @@ module Hanami
114
114
  #
115
115
  # @since 0.1.0
116
116
  # @api private
117
- HTTP_ACCEPT = "HTTP_ACCEPT"
117
+ HTTP_ACCEPT = "HTTP_ACCEPT"
118
118
 
119
119
  # The header key to set the mime type of the response
120
120
  #
121
121
  # @since 0.1.0
122
122
  # @api private
123
- CONTENT_TYPE = ::Rack::CONTENT_TYPE
123
+ CONTENT_TYPE = ::Rack::CONTENT_TYPE
124
124
 
125
125
  # The default mime type for an incoming HTTP request
126
126
  #
@@ -152,7 +152,7 @@ module Hanami
152
152
  #
153
153
  # @since 2.0.0
154
154
  # @api private
155
- ETAG = ::Rack::ETAG
155
+ ETAG = ::Rack::ETAG
156
156
 
157
157
  # @since 2.0.0
158
158
  # @api private
@@ -221,7 +221,7 @@ module Hanami
221
221
  #
222
222
  # @since 2.0.0
223
223
  # @api private
224
- COOKIE_HASH_KEY = ::Rack::RACK_REQUEST_COOKIE_HASH
224
+ COOKIE_HASH_KEY = ::Rack::RACK_REQUEST_COOKIE_HASH
225
225
 
226
226
  # The key used by Rack to set the cookies as a String in the env
227
227
  #
@@ -233,7 +233,7 @@ module Hanami
233
233
  #
234
234
  # @since 2.0.0
235
235
  # @api private
236
- RACK_INPUT = ::Rack::RACK_INPUT
236
+ RACK_INPUT = ::Rack::RACK_INPUT
237
237
 
238
238
  # The key that returns router params from the Rack env
239
239
  # This is a builtin integration for Hanami::Router
@@ -250,12 +250,6 @@ module Hanami
250
250
 
251
251
  # @since 2.0.0
252
252
  # @api private
253
- DEFAULT_CHARSET = "utf-8"
254
-
255
- # The key that returns content mime type from the Rack env
256
- #
257
- # @since 2.0.0
258
- # @api private
259
- HTTP_CONTENT_TYPE = "CONTENT_TYPE"
253
+ DEFAULT_CHARSET = "utf-8"
260
254
  end
261
255
  end
@@ -96,6 +96,7 @@ module Hanami
96
96
  # @api private
97
97
  def self.included(action)
98
98
  unless Hanami.respond_to?(:env?) && Hanami.env?(:test)
99
+ action.include Hanami::Action::Session
99
100
  action.class_eval do
100
101
  before :set_csrf_token, :verify_csrf_token
101
102
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Action
5
+ # @since 2.0.0
6
+ class Error < ::StandardError
7
+ end
8
+
9
+ # Missing session error
10
+ #
11
+ # This error is raised when `session` or `flash` is accessed/set on request/response objects
12
+ # in actions which do not include `Hanami::Action::Session`.
13
+ #
14
+ # @since 2.0.0
15
+ #
16
+ # @see Hanami::Action::Session
17
+ # @see Hanami::Action::Request#session
18
+ # @see Hanami::Action::Response#session
19
+ # @see Hanami::Action::Response#flash
20
+ class MissingSessionError < Error
21
+ def initialize(session_method)
22
+ super(<<~TEXT)
23
+ Sessions are not enabled. To use `#{session_method}`:
24
+
25
+ Configure sessions in your Hanami app, e.g.
26
+
27
+ module MyApp
28
+ class App < Hanami::App
29
+ # See Rack::Session::Cookie for options
30
+ config.sessions = :cookie, {**cookie_session_options}
31
+ end
32
+ end
33
+
34
+ Or include session support directly in your action class:
35
+
36
+ include Hanami::Action::Session
37
+ TEXT
38
+ end
39
+ end
40
+ end
41
+ end
@@ -84,13 +84,13 @@ module Hanami
84
84
  #
85
85
  # @since 2.0.0
86
86
  # @api private
87
- def self.content_type(configuration, request, accepted_mime_types)
87
+ def self.content_type(config, request, accepted_mime_types)
88
88
  if request.accept_header?
89
89
  type = best_q_match(request.accept, accepted_mime_types)
90
90
  return type if type
91
91
  end
92
92
 
93
- default_response_type(configuration) || default_content_type(configuration) || Action::DEFAULT_CONTENT_TYPE
93
+ default_response_type(config) || default_content_type(config) || Action::DEFAULT_CONTENT_TYPE
94
94
  end
95
95
 
96
96
  # @since 2.0.0
@@ -101,22 +101,22 @@ module Hanami
101
101
 
102
102
  # @since 2.0.0
103
103
  # @api private
104
- def self.default_response_type(configuration)
105
- format_to_mime_type(configuration.default_response_format, configuration)
104
+ def self.default_response_type(config)
105
+ format_to_mime_type(config.default_response_format, config)
106
106
  end
107
107
 
108
108
  # @since 2.0.0
109
109
  # @api private
110
- def self.default_content_type(configuration)
111
- format_to_mime_type(configuration.default_request_format, configuration)
110
+ def self.default_content_type(config)
111
+ format_to_mime_type(config.default_request_format, config)
112
112
  end
113
113
 
114
114
  # @since 2.0.0
115
115
  # @api private
116
- def self.format_to_mime_type(format, configuration)
116
+ def self.format_to_mime_type(format, config)
117
117
  return if format.nil?
118
118
 
119
- configuration.mime_type_for(format) ||
119
+ config.mime_type_for(format) ||
120
120
  TYPES.fetch(format) { raise Hanami::Controller::UnknownFormatError.new(format) }
121
121
  end
122
122
 
@@ -125,17 +125,17 @@ module Hanami
125
125
  #
126
126
  # @see Hanami::Action::Mime#finish
127
127
  # @example
128
- # detect_format("text/html; charset=utf-8", configuration) #=> :html
128
+ # detect_format("text/html; charset=utf-8", config) #=> :html
129
129
  #
130
130
  # @return [Symbol, nil]
131
131
  #
132
132
  # @since 2.0.0
133
133
  # @api private
134
- def self.detect_format(content_type, configuration)
134
+ def self.detect_format(content_type, config)
135
135
  return if content_type.nil?
136
136
 
137
137
  ct = content_type.split(";").first
138
- configuration.format_for(ct) || format_for(ct)
138
+ config.format_for(ct) || format_for(ct)
139
139
  end
140
140
 
141
141
  # @since 2.0.0
@@ -146,7 +146,7 @@ module Hanami
146
146
 
147
147
  # Transforms symbols to MIME Types
148
148
  # @example
149
- # restrict_mime_types(configuration, [:json]) #=> ["application/json"]
149
+ # restrict_mime_types(config, [:json]) #=> ["application/json"]
150
150
  #
151
151
  # @return [Array<String>, nil]
152
152
  #
@@ -154,35 +154,71 @@ module Hanami
154
154
  #
155
155
  # @since 2.0.0
156
156
  # @api private
157
- def self.restrict_mime_types(configuration, accepted_formats)
158
- return if accepted_formats.empty?
157
+ def self.restrict_mime_types(config)
158
+ return if config.accepted_formats.empty?
159
159
 
160
- mime_types = accepted_formats.map do |format|
161
- format_to_mime_type(format, configuration)
160
+ mime_types = config.accepted_formats.map do |format|
161
+ format_to_mime_type(format, config)
162
162
  end
163
163
 
164
- accepted_mime_types = mime_types & configuration.mime_types
164
+ accepted_mime_types = mime_types & config.mime_types
165
165
 
166
166
  return if accepted_mime_types.empty?
167
167
 
168
168
  accepted_mime_types
169
169
  end
170
170
 
171
- # Use for checking the Content-Type header to make sure is valid based
172
- # on the accepted_mime_types
171
+ # Yields if an action is configured with `accepted_formats`, the request has an `Accept`
172
+ # header, and none of the Accept types matches the accepted formats. The given block is
173
+ # expected to halt the request handling.
173
174
  #
174
- # If no Content-Type is sent in the request it will check the default_request_format
175
+ # If any of these conditions are not met, then the request is acceptable and the method
176
+ # returns without yielding.
175
177
  #
176
- # @return [TrueClass, FalseClass]
178
+ # @see Action#enforce_accepted_mime_types
179
+ # @see Action.accept
180
+ # @see Config#accepted_formats
177
181
  #
178
182
  # @since 2.0.0
179
183
  # @api private
180
- def self.accepted_mime_type?(request, accepted_mime_types, configuration)
181
- mime_type = request.env[Action::HTTP_CONTENT_TYPE] ||
182
- default_content_type(configuration) ||
183
- Action::DEFAULT_CONTENT_TYPE
184
+ def self.enforce_accept(request, config)
185
+ return unless request.accept_header?
184
186
 
185
- !accepted_mime_types.find { |mt| ::Rack::Mime.match?(mt, mime_type) }.nil?
187
+ accept_types = ::Rack::Utils.q_values(request.accept).map(&:first)
188
+ return if accept_types.any? { |mime_type| accepted_mime_type?(mime_type, config) }
189
+
190
+ yield
191
+ end
192
+
193
+ # Yields if an action is configured with `accepted_formats`, the request has a `Content-Type`
194
+ # header (or a `default_requst_format` is configured), and the content type does not match the
195
+ # accepted formats. The given block is expected to halt the request handling.
196
+ #
197
+ # If any of these conditions are not met, then the request is acceptable and the method
198
+ # returns without yielding.
199
+ #
200
+ # @see Action#enforce_accepted_mime_types
201
+ # @see Action.accept
202
+ # @see Config#accepted_formats
203
+ #
204
+ # @since 2.0.0
205
+ # @api private
206
+ def self.enforce_content_type(request, config)
207
+ content_type = request.content_type || default_content_type(config)
208
+
209
+ return if content_type.nil?
210
+
211
+ return if accepted_mime_type?(content_type, config)
212
+
213
+ yield
214
+ end
215
+
216
+ # @since 2.0.0
217
+ # @api private
218
+ def self.accepted_mime_type?(mime_type, config)
219
+ config.accepted_mime_types.any? { |accepted_mime_type|
220
+ ::Rack::Mime.match?(accepted_mime_type, mime_type)
221
+ }
186
222
  end
187
223
 
188
224
  # Use for setting the content_type and charset if the response
@@ -193,14 +229,12 @@ module Hanami
193
229
  #
194
230
  # @since 2.0.0
195
231
  # @api private
196
- def self.calculate_content_type_with_charset(configuration, request, accepted_mime_types)
197
- charset = self.charset(configuration.default_charset)
198
- content_type = self.content_type(configuration, request, accepted_mime_types)
232
+ def self.calculate_content_type_with_charset(config, request, accepted_mime_types)
233
+ charset = self.charset(config.default_charset)
234
+ content_type = self.content_type(config, request, accepted_mime_types)
199
235
  content_type_with_charset(content_type, charset)
200
236
  end
201
237
 
202
- # private
203
-
204
238
  # Patched version of <tt>Rack::Utils.best_q_match</tt>.
205
239
  #
206
240
  # @since 2.0.0
@@ -25,7 +25,7 @@ module Hanami
25
25
  # @since 1.1.0
26
26
  # @api private
27
27
  def initialize(errors = {})
28
- super(errors)
28
+ super(errors.dup)
29
29
  end
30
30
 
31
31
  # Add an error to the param validations
@@ -160,9 +160,9 @@ module Hanami
160
160
  def initialize(env)
161
161
  @env = env
162
162
  super(_extract_params)
163
- @result = validate
164
- @params = _params
165
- @errors = Errors.new(@result.messages)
163
+ validation = validate
164
+ @params = validation.to_h
165
+ @errors = Errors.new(validation.messages)
166
166
  freeze
167
167
  end
168
168
 
@@ -235,7 +235,7 @@ module Hanami
235
235
  errors.empty?
236
236
  end
237
237
 
238
- # Serialize params to Hash
238
+ # Serialize validated params to Hash
239
239
  #
240
240
  # @return [::Hash]
241
241
  #
@@ -244,13 +244,6 @@ module Hanami
244
244
  @params
245
245
  end
246
246
  alias_method :to_hash, :to_h
247
-
248
- private
249
-
250
- # @api private
251
- def _params
252
- _router_params.merge(@result.output)
253
- end
254
247
  end
255
248
  end
256
249
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rack/utils"
3
+ require "hanami/action/flash"
4
4
  require "rack/mime"
5
5
  require "rack/request"
6
+ require "rack/utils"
6
7
  require "securerandom"
7
8
 
8
9
  module Hanami
@@ -16,9 +17,11 @@ module Hanami
16
17
  class Request < ::Rack::Request
17
18
  attr_reader :params
18
19
 
19
- def initialize(env, params)
20
+ def initialize(env:, params:, sessions_enabled: false)
20
21
  super(env)
22
+
21
23
  @params = params
24
+ @sessions_enabled = sessions_enabled
22
25
  end
23
26
 
24
27
  def id
@@ -26,6 +29,22 @@ module Hanami
26
29
  @id ||= @env[Action::REQUEST_ID] = SecureRandom.hex(Action::DEFAULT_ID_LENGTH)
27
30
  end
28
31
 
32
+ def session
33
+ unless @sessions_enabled
34
+ raise Hanami::Action::MissingSessionError.new("Hanami::Action::Request#session")
35
+ end
36
+
37
+ super
38
+ end
39
+
40
+ def flash
41
+ unless @sessions_enabled
42
+ raise Hanami::Action::MissingSessionError.new("Hanami::Action::Request#flash")
43
+ end
44
+
45
+ @flash ||= Flash.new(session[Flash::KEY])
46
+ end
47
+
29
48
  def accept?(mime_type)
30
49
  !!::Rack::Utils.q_values(accept).find do |mime, _|
31
50
  ::Rack::Mime.match?(mime_type, mime)