hanami-controller 2.0.0.beta1 → 2.0.0.rc1

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