hanami-controller 2.0.0.beta1 → 2.0.0.rc1

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: 0a5d623c09e8ecb8c267de856196333c5da74e066b55501af2acc706349d0d73
4
+ data.tar.gz: '0786076a81b7a11ca79aedba2ff0318f207683e71ba931293dcc1f585a191e0b'
5
5
  SHA512:
6
- metadata.gz: 5522815b014bd6bf4ad6750a12c80a6db7d1aaab15562004a7898bc6cf5a5f0bbfa93d171cee31268685268ead3d37d6aa42be1bfc780c7a3bba2207814df087
7
- data.tar.gz: 379911174cd6deb4637e6dcb80731d1d221f6bb4f3c184001f6f526e8d87cdf385aa0e114a1c4fa78c2dc3361015074b5afbe428a2b630a8ff4cd17f180e5908
6
+ metadata.gz: 81f1cddefe11892d05ccb63e8649bc9fb6e50f2258eb02503044577e9c0e445c57cbf2318046a4002e3c9b107f9df530463cbeb10460bb3339bb52b747deb0eb
7
+ data.tar.gz: eb26f557543e0d345580b9160ec4123def85b4a4ec5a1ad0a17f8a8d15d05dc34f12e13705082e21b95a5cc3095d36677b099a319a1c2472c7180e413f5c5faf
data/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  Complete, fast and testable actions for Rack
4
4
 
5
+ ## v2.0.0.rc1 - 2022-11-08
6
+
7
+ ### Changed
8
+
9
+ - [Tim Riley] Simplify assignment of response format: `response.format = :json` (was `response.format = format(:json)`)
10
+
11
+ ## v2.0.0.beta4 - 2022-10-24
12
+
13
+ ### Added
14
+
15
+ - [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)
16
+
17
+ ### Fixed
18
+
19
+ - [Benjamin Klotz] When a params validation schema is provided (in a `params do` block), only return the validated params from `request.params` (#375)
20
+ - [Sean Collins] Handle dry-schema's messages hash now being frozen by default (#391)
21
+
22
+ ### Changed
23
+
24
+ - [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)
25
+ - [Tim Riley] Add `Action.handle_exception` class method as a shortcut for `Hanami::Action::Config#handle_exception` (#394)
26
+ - [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)
27
+ - [Tim Riley] Make params validation schemas (defined in `params do` block) inheritable to subclasses (#394)
28
+ - [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)
29
+
5
30
  ## v2.0.0.beta1 - 2022-07-20
6
31
 
7
32
  ### Fixed
data/README.md CHANGED
@@ -744,7 +744,7 @@ However, you can force this value:
744
744
  class Show < Hanami::Action
745
745
  def handle(*, res)
746
746
  # ...
747
- res.format = format(:json)
747
+ res.format = :json
748
748
  end
749
749
  end
750
750
 
@@ -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.
@@ -820,7 +820,7 @@ response.format # => :custom
820
820
  class Show < Hanami::Action
821
821
  def handle(*, res)
822
822
  # ...
823
- res.format = format(:custom)
823
+ res.format = :custom
824
824
  end
825
825
  end
826
826
 
@@ -841,7 +841,7 @@ end
841
841
 
842
842
  class Csv < Hanami::Action
843
843
  def handle(*, res)
844
- res.format = format(:csv)
844
+ res.format = :csv
845
845
  res.body = Enumerator.new do |yielder|
846
846
  yielder << csv_header
847
847
 
@@ -21,8 +21,8 @@ Gem::Specification.new do |spec|
21
21
  spec.required_ruby_version = ">= 3.0"
22
22
 
23
23
  spec.add_dependency "rack", "~> 2.0"
24
- spec.add_dependency "hanami-utils", "~> 2.0.beta"
25
- spec.add_dependency "dry-configurable", "~> 0.13", ">= 0.13.0"
24
+ spec.add_dependency "hanami-utils", "~> 2.0.0.rc1"
25
+ spec.add_dependency "dry-configurable", "~> 1.0", "< 2"
26
26
 
27
27
  spec.add_development_dependency "bundler", ">= 1.6", "< 3"
28
28
  spec.add_development_dependency "rack-test", "~> 2.0"
@@ -5,6 +5,19 @@ require "hanami/utils/hash"
5
5
 
6
6
  module Hanami
7
7
  class Action
8
+ # Provides access to params included in a Rack request.
9
+ #
10
+ # Offers useful access to params via methods like {#[]}, {#get} and {#to_h}.
11
+ #
12
+ # These params are available via {Request#params}.
13
+ #
14
+ # This class is used by default when {Hanami::Action::Validatable} is not included, or when no
15
+ # {Validatable::ClassMethods#params params} validation schema is defined.
16
+ #
17
+ # @see Hanami::Action::Request#params
18
+ #
19
+ # @api private
20
+ # @since 0.7.0
8
21
  class BaseParams
9
22
  # @attr_reader env [Hash] the Rack env
10
23
  #
@@ -18,12 +31,10 @@ module Hanami
18
31
  # @api private
19
32
  attr_reader :raw
20
33
 
21
- # Initialize the params and freeze them.
34
+ # Returns a new frozen params object for the Rack env.
22
35
  #
23
36
  # @param env [Hash] a Rack env or an hash of params.
24
37
  #
25
- # @return [Params]
26
- #
27
38
  # @since 0.7.0
28
39
  # @api private
29
40
  def initialize(env)
@@ -33,26 +44,27 @@ module Hanami
33
44
  freeze
34
45
  end
35
46
 
36
- # Returns the object associated with the given key
47
+ # Returns the value for the given params key.
37
48
  #
38
49
  # @param key [Symbol] the key
39
50
  #
40
- # @return [Object,nil] return the associated object, if found
51
+ # @return [Object,nil] the associated value, if found
41
52
  #
42
53
  # @since 0.7.0
54
+ # @api public
43
55
  def [](key)
44
56
  @params[key]
45
57
  end
46
58
 
47
- # Get an attribute value associated with the given key.
48
- # Nested attributes are reached by listing all the keys to get to the value.
59
+ # Returns an value associated with the given params key.
60
+ #
61
+ # You can access nested attributes by listing all the keys in the path. This uses the same key
62
+ # path semantics as `Hash#dig`.
49
63
  #
50
64
  # @param keys [Array<Symbol,Integer>] the key
51
65
  #
52
66
  # @return [Object,NilClass] return the associated value, if found
53
67
  #
54
- # @since 0.7.0
55
- #
56
68
  # @example
57
69
  # require "hanami/controller"
58
70
  #
@@ -73,6 +85,9 @@ module Hanami
73
85
  # end
74
86
  # end
75
87
  # end
88
+ #
89
+ # @since 0.7.0
90
+ # @api public
76
91
  def get(*keys)
77
92
  @params.dig(*keys)
78
93
  end
@@ -83,32 +98,40 @@ module Hanami
83
98
  # @since 0.8.0
84
99
  alias_method :dig, :get
85
100
 
86
- # Provide a common interface with Params
101
+ # Returns true at all times, providing a common interface with {Params}.
87
102
  #
88
103
  # @return [TrueClass] always returns true
89
104
  #
90
- # @since 0.7.0
91
- #
92
105
  # @see Hanami::Action::Params#valid?
106
+ #
107
+ # @api public
108
+ # @since 0.7.0
93
109
  def valid?
94
110
  true
95
111
  end
96
112
 
97
- # Serialize params to Hash
113
+ # Returns a hash of the parsed request params.
98
114
  #
99
- # @return [::Hash]
115
+ # @return [Hash]
100
116
  #
101
117
  # @since 0.7.0
118
+ # @api public
102
119
  def to_h
103
120
  @params
104
121
  end
105
122
  alias_method :to_hash, :to_h
106
123
 
107
- # Iterates through params
124
+ # Iterates over the params.
125
+ #
126
+ # Calls the given block with each param key-value pair; returns the full hash of params.
127
+ #
128
+ # @yieldparam key [Symbol]
129
+ # @yieldparam value [Object]
108
130
  #
109
- # @param blk [Proc]
131
+ # @return [to_h]
110
132
  #
111
133
  # @since 0.7.1
134
+ # @api public
112
135
  def each(&blk)
113
136
  to_h.each(&blk)
114
137
  end
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require_relative "mime"
5
+
6
+ module Hanami
7
+ class Action
8
+ # Config for `Hanami::Action` classes.
9
+ #
10
+ # @see Hanami::Action.config
11
+ #
12
+ # @api public
13
+ # @since 2.0.0
14
+ class Config < Dry::Configurable::Config
15
+ # Default MIME type to format mapping
16
+ #
17
+ # @since 0.2.0
18
+ # @api private
19
+ DEFAULT_FORMATS = {
20
+ "application/octet-stream" => :all,
21
+ "*/*" => :all,
22
+ "text/html" => :html
23
+ }.freeze
24
+
25
+ # Default public directory
26
+ #
27
+ # This serves as the root directory for file downloads
28
+ #
29
+ # @since 1.0.0
30
+ #
31
+ # @api private
32
+ DEFAULT_PUBLIC_DIRECTORY = "public"
33
+
34
+ # @!attribute [rw] handled_exceptions
35
+ #
36
+ # Specifies how to handle exceptions with an HTTP status.
37
+ #
38
+ # Raised exceptions will return the corresponding HTTP status.
39
+ #
40
+ # @return [Hash{Exception=>Integer}] exception classes as keys and HTTP statuses as values
41
+ #
42
+ # @example
43
+ # config.handled_exceptions = {ArgumentError => 400}
44
+ #
45
+ # @since 0.2.0
46
+
47
+ # Specifies how to handle exceptions with an HTTP status
48
+ #
49
+ # Raised exceptions will return the corresponding HTTP status
50
+ #
51
+ # The specified exceptions will be merged with any previously configured
52
+ # exceptions
53
+ #
54
+ # @param exceptions [Hash{Exception=>Integer}] exception classes as keys
55
+ # and HTTP statuses as values
56
+ #
57
+ # @return [void]
58
+ #
59
+ # @example
60
+ # config.handle_exceptions(ArgumentError => 400}
61
+ #
62
+ # @see handled_exceptions
63
+ #
64
+ # @since 0.2.0
65
+ def handle_exception(exceptions)
66
+ self.handled_exceptions = handled_exceptions
67
+ .merge(exceptions)
68
+ .sort { |(ex1, _), (ex2, _)| ex1.ancestors.include?(ex2) ? -1 : 1 }
69
+ .to_h
70
+ end
71
+
72
+ # @!attribute [rw] formats
73
+ #
74
+ # Specifies the MIME type to format mapping
75
+ #
76
+ # @return [Hash{String=>Symbol}] MIME type strings as keys and format symbols as values
77
+ #
78
+ # @see format
79
+ # @see Hanami::Action::Mime
80
+ #
81
+ # @example
82
+ # config.formats = {"text/html" => :html}
83
+ #
84
+ # @since 0.2.0
85
+
86
+ # Registers a MIME type to format mapping
87
+ #
88
+ # @param hash [Hash{Symbol=>String}] format symbols as keys and the MIME
89
+ # type strings must as values
90
+ #
91
+ # @return [void]
92
+ #
93
+ # @see formats
94
+ # @see Hanami::Action::Mime
95
+ #
96
+ # @example
97
+ # config.format html: "text/html"
98
+ #
99
+ # @since 0.2.0
100
+ def format(hash)
101
+ symbol, mime_type = *Utils::Kernel.Array(hash)
102
+ formats[Utils::Kernel.String(mime_type)] = Utils::Kernel.Symbol(symbol)
103
+ end
104
+
105
+ # Returns the configured format for the given MIME type
106
+ #
107
+ # @param mime_type [#to_s,#to_str] A mime type
108
+ #
109
+ # @return [Symbol,nil] the corresponding format, nil if not found
110
+ #
111
+ # @see format
112
+ #
113
+ # @since 0.2.0
114
+ # @api private
115
+ def format_for(mime_type)
116
+ formats[mime_type]
117
+ end
118
+
119
+ # Returns the configured format's MIME types
120
+ #
121
+ # @return [Array<String>] the format's MIME types
122
+ #
123
+ # @see formats=
124
+ # @see format
125
+ #
126
+ # @since 0.8.0
127
+ #
128
+ # @api private
129
+ def mime_types
130
+ # FIXME: this isn't efficient. speed it up!
131
+ ((formats.keys - DEFAULT_FORMATS.keys) +
132
+ Hanami::Action::Mime::TYPES.values).freeze
133
+ end
134
+
135
+ # Returns a MIME type for the given format
136
+ #
137
+ # @param format [#to_sym] a format
138
+ #
139
+ # @return [String,nil] the corresponding MIME type, if present
140
+ #
141
+ # @since 0.2.0
142
+ # @api private
143
+ def mime_type_for(format)
144
+ formats.key(format)
145
+ end
146
+
147
+ # @since 2.0.0
148
+ # @api private
149
+ def accepted_mime_types
150
+ accepted_formats.any? ? Mime.restrict_mime_types(self) : mime_types
151
+ end
152
+
153
+ # @!attribute [rw] default_request_format
154
+ #
155
+ # Sets a format as default fallback for all the requests without a strict
156
+ # requirement for the MIME type.
157
+ #
158
+ # The given format must be coercible to a symbol, and be a valid MIME
159
+ # type alias. If it isn't, at runtime the framework will raise an
160
+ # `Hanami::Action::UnknownFormatError`.
161
+ #
162
+ # By default, this value is nil.
163
+ #
164
+ # @return [Symbol]
165
+ #
166
+ # @see Hanami::Action::Mime
167
+ #
168
+ # @since 0.5.0
169
+
170
+ # @!attribute [rw] default_response_format
171
+ #
172
+ # Sets a format to be used for all responses regardless of the request
173
+ # type.
174
+ #
175
+ # The given format must be coercible to a symbol, and be a valid MIME
176
+ # type alias. If it isn't, at the runtime the framework will raise an
177
+ # `Hanami::Action::UnknownFormatError`.
178
+ #
179
+ # By default, this value is nil.
180
+ #
181
+ # @return [Symbol]
182
+ #
183
+ # @see Hanami::Action::Mime
184
+ #
185
+ # @since 0.5.0
186
+
187
+ # @!attribute [rw] default_charset
188
+ #
189
+ # Sets a charset (character set) as default fallback for all the requests
190
+ # without a strict requirement for the charset.
191
+ #
192
+ # By default, this value is nil.
193
+ #
194
+ # @return [String]
195
+ #
196
+ # @see Hanami::Action::Mime
197
+ #
198
+ # @since 0.3.0
199
+
200
+ # @!attribute [rw] default_headers
201
+ #
202
+ # Sets default headers for all responses.
203
+ #
204
+ # By default, this is an empty hash.
205
+ #
206
+ # @return [Hash{String=>String}] the headers
207
+ #
208
+ # @example
209
+ # config.default_headers = {"X-Frame-Options" => "DENY"}
210
+ #
211
+ # @see default_headers
212
+ #
213
+ # @since 0.4.0
214
+
215
+ # @!attribute [rw] cookies
216
+ #
217
+ # Sets default cookie options for all responses.
218
+ #
219
+ # By default this, is an empty hash.
220
+ #
221
+ # @return [Hash{Symbol=>String}] the cookie options
222
+ #
223
+ # @example
224
+ # config.cookies = {
225
+ # domain: "hanamirb.org",
226
+ # path: "/controller",
227
+ # secure: true,
228
+ # httponly: true
229
+ # }
230
+ #
231
+ # @since 0.4.0
232
+
233
+ # @!attribute [rw] root_directory
234
+ #
235
+ # Sets the the for the public directory, which is used for file downloads.
236
+ # This must be an existent directory.
237
+ #
238
+ # Defaults to the current working directory.
239
+ #
240
+ # @return [String] the directory path
241
+ #
242
+ # @api private
243
+ #
244
+ # @since 1.0.0
245
+
246
+ # @!attribute [rw] public_directory
247
+ #
248
+ # Sets the path to public directory. This directory is used for file downloads.
249
+ #
250
+ # This given directory will be appended onto the root directory.
251
+ #
252
+ # By default, the public directory is `"public"`.
253
+ # @return [String] the public directory path
254
+ #
255
+ # @example
256
+ # config.public_directory = "public"
257
+ # config.public_directory # => "/path/to/root/public"
258
+ #
259
+ # @see root_directory
260
+ #
261
+ # @since 2.0.0
262
+ def public_directory
263
+ # This must be a string, for Rack compatibility
264
+ root_directory.join(super).to_s
265
+ end
266
+ end
267
+ end
268
+ end
@@ -114,13 +114,7 @@ module Hanami
114
114
  #
115
115
  # @since 0.1.0
116
116
  # @api private
117
- HTTP_ACCEPT = "HTTP_ACCEPT"
118
-
119
- # The header key to set the mime type of the response
120
- #
121
- # @since 0.1.0
122
- # @api private
123
- CONTENT_TYPE = ::Rack::CONTENT_TYPE
117
+ HTTP_ACCEPT = "HTTP_ACCEPT"
124
118
 
125
119
  # The default mime type for an incoming HTTP request
126
120
  #
@@ -152,7 +146,7 @@ module Hanami
152
146
  #
153
147
  # @since 2.0.0
154
148
  # @api private
155
- ETAG = ::Rack::ETAG
149
+ ETAG = ::Rack::ETAG
156
150
 
157
151
  # @since 2.0.0
158
152
  # @api private
@@ -221,7 +215,7 @@ module Hanami
221
215
  #
222
216
  # @since 2.0.0
223
217
  # @api private
224
- COOKIE_HASH_KEY = ::Rack::RACK_REQUEST_COOKIE_HASH
218
+ COOKIE_HASH_KEY = ::Rack::RACK_REQUEST_COOKIE_HASH
225
219
 
226
220
  # The key used by Rack to set the cookies as a String in the env
227
221
  #
@@ -233,7 +227,7 @@ module Hanami
233
227
  #
234
228
  # @since 2.0.0
235
229
  # @api private
236
- RACK_INPUT = ::Rack::RACK_INPUT
230
+ RACK_INPUT = ::Rack::RACK_INPUT
237
231
 
238
232
  # The key that returns router params from the Rack env
239
233
  # This is a builtin integration for Hanami::Router
@@ -250,12 +244,6 @@ module Hanami
250
244
 
251
245
  # @since 2.0.0
252
246
  # @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"
247
+ DEFAULT_CHARSET = "utf-8"
260
248
  end
261
249
  end
@@ -1,19 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hanami/utils/blank"
4
- require "hanami/controller/error"
5
4
  require "rack/utils"
6
5
  require "securerandom"
6
+ require_relative "errors"
7
7
 
8
8
  module Hanami
9
9
  # @api private
10
10
  class Action
11
- # Invalid CSRF Token
12
- #
13
- # @since 0.4.0
14
- class InvalidCSRFTokenError < Controller::Error
15
- end
16
-
17
11
  # CSRF Protection
18
12
  #
19
13
  # This security mechanism is enabled automatically if sessions are turned on.
@@ -96,6 +90,7 @@ module Hanami
96
90
  # @api private
97
91
  def self.included(action)
98
92
  unless Hanami.respond_to?(:env?) && Hanami.env?(:test)
93
+ action.include Hanami::Action::Session
99
94
  action.class_eval do
100
95
  before :set_csrf_token, :verify_csrf_token
101
96
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Action
5
+ # Base class for all Action errors.
6
+ #
7
+ # @api public
8
+ # @since 2.0.0
9
+ class Error < ::StandardError
10
+ end
11
+
12
+ # Unknown format error
13
+ #
14
+ # This error is raised when a action sets a format that it isn't recognized
15
+ # both by `Hanami::Action::Configuration` and the list of Rack mime types
16
+ #
17
+ # @since 2.0.0
18
+ #
19
+ # @see Hanami::Action::Mime#format=
20
+ class UnknownFormatError < Error
21
+ # @since 2.0.0
22
+ # @api private
23
+ def initialize(format)
24
+ super("Cannot find a corresponding Mime type for '#{format}'. Please configure it with Hanami::Controller::Configuration#format.") # rubocop:disable Layout/LineLength
25
+ end
26
+ end
27
+
28
+ # Error raised when session is accessed but not enabled.
29
+ #
30
+ # This error is raised when `session` or `flash` is accessed/set on request/response objects
31
+ # in actions which do not include `Hanami::Action::Session`.
32
+ #
33
+ # @see Hanami::Action::Session
34
+ # @see Hanami::Action::Request#session
35
+ # @see Hanami::Action::Response#session
36
+ # @see Hanami::Action::Response#flash
37
+ #
38
+ # @api public
39
+ # @since 2.0.0
40
+ class MissingSessionError < Error
41
+ # @api private
42
+ # @since 2.0.0
43
+ def initialize(session_method)
44
+ super(<<~TEXT)
45
+ Sessions are not enabled. To use `#{session_method}`:
46
+
47
+ Configure sessions in your Hanami app, e.g.
48
+
49
+ module MyApp
50
+ class App < Hanami::App
51
+ # See Rack::Session::Cookie for options
52
+ config.sessions = :cookie, {**cookie_session_options}
53
+ end
54
+ end
55
+
56
+ Or include session support directly in your action class:
57
+
58
+ include Hanami::Action::Session
59
+ TEXT
60
+ end
61
+ end
62
+
63
+ # Invalid CSRF Token
64
+ #
65
+ # @since 0.4.0
66
+ class InvalidCSRFTokenError < Error
67
+ end
68
+ end
69
+ end