hanami-controller 2.0.0.beta4 → 2.0.0
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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +3 -9
- data/hanami-controller.gemspec +5 -3
- data/lib/hanami/action/base_params.rb +39 -16
- data/lib/hanami/action/cache/cache_control.rb +0 -2
- data/lib/hanami/action/cache/expires.rb +0 -2
- data/lib/hanami/action/cache.rb +0 -4
- data/lib/hanami/action/config/formats.rb +216 -0
- data/lib/hanami/action/config.rb +24 -113
- data/lib/hanami/action/constants.rb +0 -6
- data/lib/hanami/action/csrf_protection.rb +1 -7
- data/lib/hanami/action/{error.rb → errors.rb} +38 -3
- data/lib/hanami/action/flash.rb +29 -22
- data/lib/hanami/action/halt.rb +3 -1
- data/lib/hanami/action/mime/request_mime_weight.rb +66 -0
- data/lib/hanami/action/mime.rb +179 -210
- data/lib/hanami/action/params.rb +0 -1
- data/lib/hanami/action/rack/file.rb +6 -2
- data/lib/hanami/action/request.rb +46 -4
- data/lib/hanami/action/response.rb +210 -17
- data/lib/hanami/action/session.rb +7 -4
- data/lib/hanami/action/validatable.rb +10 -6
- data/lib/hanami/action.rb +116 -143
- data/lib/hanami/controller/version.rb +5 -2
- data/lib/hanami/controller.rb +2 -30
- data/lib/hanami/http/status.rb +2 -0
- data/lib/hanami-controller.rb +3 -0
- metadata +44 -14
- data/lib/hanami/controller/error.rb +0 -9
@@ -3,14 +3,18 @@
|
|
3
3
|
require "rack"
|
4
4
|
require "rack/response"
|
5
5
|
require "hanami/utils/kernel"
|
6
|
-
|
7
|
-
require "hanami/action/cookie_jar"
|
8
|
-
require "hanami/action/cache/cache_control"
|
9
|
-
require "hanami/action/cache/expires"
|
10
|
-
require "hanami/action/cache/conditional_get"
|
6
|
+
require_relative "errors"
|
11
7
|
|
12
8
|
module Hanami
|
13
9
|
class Action
|
10
|
+
# The HTTP response for an action, given to {Action#handle}.
|
11
|
+
#
|
12
|
+
# Inherits from `Rack::Response`, providing compatibility with Rack functionality.
|
13
|
+
#
|
14
|
+
# @see http://www.rubydoc.info/gems/rack/Rack/Response
|
15
|
+
#
|
16
|
+
# @since 2.0.0
|
17
|
+
# @api private
|
14
18
|
class Response < ::Rack::Response
|
15
19
|
# @since 2.0.0
|
16
20
|
# @api private
|
@@ -26,7 +30,7 @@ module Hanami
|
|
26
30
|
|
27
31
|
# @since 2.0.0
|
28
32
|
# @api private
|
29
|
-
attr_reader :request, :exposures, :
|
33
|
+
attr_reader :request, :exposures, :env, :view_options
|
30
34
|
|
31
35
|
# @since 2.0.0
|
32
36
|
# @api private
|
@@ -35,10 +39,10 @@ module Hanami
|
|
35
39
|
# @since 2.0.0
|
36
40
|
# @api private
|
37
41
|
def self.build(status, env)
|
38
|
-
new(config:
|
42
|
+
new(config: Action.config.dup, content_type: Mime.best_q_match(env[Action::HTTP_ACCEPT]), env: env).tap do |r|
|
39
43
|
r.status = status
|
40
44
|
r.body = Http::Status.message_for(status)
|
41
|
-
r.set_format(Mime.
|
45
|
+
r.set_format(Mime.detect_format(r.content_type), config)
|
42
46
|
end
|
43
47
|
end
|
44
48
|
|
@@ -46,7 +50,7 @@ module Hanami
|
|
46
50
|
# @api private
|
47
51
|
def initialize(request:, config:, content_type: nil, env: {}, headers: {}, view_options: nil, sessions_enabled: false) # rubocop:disable Layout/LineLength, Metrics/ParameterLists
|
48
52
|
super([], 200, headers.dup)
|
49
|
-
|
53
|
+
self.content_type = content_type if content_type
|
50
54
|
|
51
55
|
@request = request
|
52
56
|
@config = config
|
@@ -59,6 +63,10 @@ module Hanami
|
|
59
63
|
@sending_file = false
|
60
64
|
end
|
61
65
|
|
66
|
+
# Sets the response body.
|
67
|
+
#
|
68
|
+
# @param str [String] the body string
|
69
|
+
#
|
62
70
|
# @since 2.0.0
|
63
71
|
# @api public
|
64
72
|
def body=(str)
|
@@ -73,32 +81,101 @@ module Hanami
|
|
73
81
|
end
|
74
82
|
end
|
75
83
|
|
76
|
-
#
|
77
|
-
#
|
84
|
+
# This is NOT RELEASED with 2.0.0
|
85
|
+
#
|
86
|
+
# @api private
|
78
87
|
def render(view, **options)
|
79
88
|
self.body = view.(**view_options.(request, self), **exposures.merge(options)).to_str
|
80
89
|
end
|
81
90
|
|
91
|
+
# Returns the format for the response.
|
92
|
+
#
|
93
|
+
# Returns nil if a format has not been assigned and also cannot be determined from the
|
94
|
+
# response's `#content_type`.
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# response.format # => :json
|
98
|
+
#
|
99
|
+
# @return [Symbol, nil]
|
100
|
+
#
|
82
101
|
# @since 2.0.0
|
83
102
|
# @api public
|
84
|
-
def format
|
85
|
-
@format
|
86
|
-
content_type = Action::Mime.content_type_with_charset(content_type, charset)
|
87
|
-
set_header("Content-Type", content_type)
|
103
|
+
def format
|
104
|
+
@format ||= Mime.detect_format(content_type, @config)
|
88
105
|
end
|
89
106
|
|
107
|
+
# Sets the format and associated content type for the response.
|
108
|
+
#
|
109
|
+
# Either a format name (`:json`) or a MIME type (`"application/json"`) may be given. In either
|
110
|
+
# case, the format or content type will be derived from the given value, and both will be set.
|
111
|
+
#
|
112
|
+
# Providing an unknown format name will raise an {Hanami::Action::UnknownFormatError}.
|
113
|
+
#
|
114
|
+
# Providing an unknown MIME type will set the content type and set the format as nil.
|
115
|
+
#
|
116
|
+
# @example Assigning via a format name symbol
|
117
|
+
# response.format = :json
|
118
|
+
# response.content_type # => "application/json"
|
119
|
+
# response.headers["Content-Type"] # => "application/json"
|
120
|
+
#
|
121
|
+
# @example Assigning via a content type string
|
122
|
+
# response.format = "application/json"
|
123
|
+
# response.format # => :json
|
124
|
+
# response.content_type # => "application/json"
|
125
|
+
#
|
126
|
+
# @param value [Symbol, String] the format name or content type
|
127
|
+
#
|
128
|
+
# @raise [Hanami::Action::UnknownFormatError] if an unknown format name is given
|
129
|
+
#
|
130
|
+
# @see Config#formats
|
131
|
+
#
|
132
|
+
# @since 2.0.0
|
133
|
+
# @api public
|
134
|
+
def format=(value)
|
135
|
+
format, content_type = Mime.detect_format_and_content_type(value, @config)
|
136
|
+
|
137
|
+
self.content_type = Mime.content_type_with_charset(content_type, charset)
|
138
|
+
|
139
|
+
@format = format
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns the exposure value for the given key.
|
143
|
+
#
|
144
|
+
# @param key [Object]
|
145
|
+
#
|
146
|
+
# @return [Object] the exposure value, if found
|
147
|
+
#
|
148
|
+
# @raise [KeyError] if the exposure was not found
|
149
|
+
#
|
90
150
|
# @since 2.0.0
|
91
151
|
# @api public
|
92
152
|
def [](key)
|
93
153
|
@exposures.fetch(key)
|
94
154
|
end
|
95
155
|
|
156
|
+
# Sets an exposure value for the given key.
|
157
|
+
#
|
158
|
+
# @param key [Object]
|
159
|
+
# @param value [Object]
|
160
|
+
#
|
161
|
+
# @return [Object] the value
|
162
|
+
#
|
96
163
|
# @since 2.0.0
|
97
164
|
# @api public
|
98
165
|
def []=(key, value)
|
99
166
|
@exposures[key] = value
|
100
167
|
end
|
101
168
|
|
169
|
+
# Returns the session for the response.
|
170
|
+
#
|
171
|
+
# This is the same session object as the {Request}.
|
172
|
+
#
|
173
|
+
# @return [Hash] the session object
|
174
|
+
#
|
175
|
+
# @raise [MissingSessionError] if sessions are not enabled
|
176
|
+
#
|
177
|
+
# @see Request#session
|
178
|
+
#
|
102
179
|
# @since 2.0.0
|
103
180
|
# @api public
|
104
181
|
def session
|
@@ -109,6 +186,16 @@ module Hanami
|
|
109
186
|
request.session
|
110
187
|
end
|
111
188
|
|
189
|
+
# Returns the flash for the request.
|
190
|
+
#
|
191
|
+
# This is the same flash object as the {Request}.
|
192
|
+
#
|
193
|
+
# @return [Flash]
|
194
|
+
#
|
195
|
+
# @raise [MissingSessionError] if sessions are not enabled
|
196
|
+
#
|
197
|
+
# @see Request#flash
|
198
|
+
#
|
112
199
|
# @since 2.0.0
|
113
200
|
# @api public
|
114
201
|
def flash
|
@@ -119,12 +206,21 @@ module Hanami
|
|
119
206
|
request.flash
|
120
207
|
end
|
121
208
|
|
209
|
+
# Returns the set of cookies to be included in the response.
|
210
|
+
#
|
211
|
+
# @return [CookieJar]
|
212
|
+
#
|
122
213
|
# @since 2.0.0
|
123
214
|
# @api public
|
124
215
|
def cookies
|
125
216
|
@cookies ||= CookieJar.new(env.dup, headers, @config.cookies)
|
126
217
|
end
|
127
218
|
|
219
|
+
# Sets the response to redirect to the given URL and halts further handling.
|
220
|
+
#
|
221
|
+
# @param url [String]
|
222
|
+
# @param status [Integer] the HTTP status to use for the redirect
|
223
|
+
#
|
128
224
|
# @since 2.0.0
|
129
225
|
# @api public
|
130
226
|
def redirect_to(url, status: 302)
|
@@ -134,6 +230,22 @@ module Hanami
|
|
134
230
|
Halt.call(status)
|
135
231
|
end
|
136
232
|
|
233
|
+
# Sends the file at the given path as the response, for any file within the configured
|
234
|
+
# `public_directory`.
|
235
|
+
#
|
236
|
+
# Handles the following aspects for file responses:
|
237
|
+
#
|
238
|
+
# - Setting `Content-Type` and `Content-Length` headers
|
239
|
+
# - File Not Found responses (returns a 404)
|
240
|
+
# - Conditional GET (via `If-Modified-Since` header)
|
241
|
+
# - Range requests (via `Range` header)
|
242
|
+
#
|
243
|
+
# @param path [String] the file path
|
244
|
+
#
|
245
|
+
# @return [void]
|
246
|
+
#
|
247
|
+
# @see Config#public_directory
|
248
|
+
#
|
137
249
|
# @since 2.0.0
|
138
250
|
# @api public
|
139
251
|
def send_file(path)
|
@@ -142,6 +254,14 @@ module Hanami
|
|
142
254
|
)
|
143
255
|
end
|
144
256
|
|
257
|
+
# Send the file at the given path as the response, for a file anywhere in the file system.
|
258
|
+
#
|
259
|
+
# @see #send_file
|
260
|
+
#
|
261
|
+
# @param path [String, Pathname] path to the file to be sent
|
262
|
+
#
|
263
|
+
# @return [void]
|
264
|
+
#
|
145
265
|
# @since 2.0.0
|
146
266
|
# @api public
|
147
267
|
def unsafe_send_file(path)
|
@@ -156,6 +276,37 @@ module Hanami
|
|
156
276
|
)
|
157
277
|
end
|
158
278
|
|
279
|
+
# Specifies the response freshness policy for HTTP caches using the `Cache-Control` header.
|
280
|
+
#
|
281
|
+
# Any number of non-value directives (`:public`, `:private`, `:no_cache`, `:no_store`,
|
282
|
+
# `:must_revalidate`, `:proxy_revalidate`) may be passed along with a Hash of value directives
|
283
|
+
# (`:max_age`, `:min_stale`, `:s_max_age`).
|
284
|
+
#
|
285
|
+
# See [RFC 2616 / 14.9](http://tools.ietf.org/html/rfc2616#section-14.9.1) for more on
|
286
|
+
# standard cache control directives.
|
287
|
+
#
|
288
|
+
# @example
|
289
|
+
# # Set Cache-Control directives
|
290
|
+
# response.cache_control :public, max_age: 900, s_maxage: 86400
|
291
|
+
#
|
292
|
+
# # Overwrite previous Cache-Control directives
|
293
|
+
# response.cache_control :private, :no_cache, :no_store
|
294
|
+
#
|
295
|
+
# response.get_header("Cache-Control") # => "private, no-store, max-age=900"
|
296
|
+
#
|
297
|
+
# @param values [Array<Symbol, Hash>] values to map to `Cache-Control` directives
|
298
|
+
# @option values [Symbol] :public
|
299
|
+
# @option values [Symbol] :private
|
300
|
+
# @option values [Symbol] :no_cache
|
301
|
+
# @option values [Symbol] :no_store
|
302
|
+
# @option values [Symbol] :must_validate
|
303
|
+
# @option values [Symbol] :proxy_revalidate
|
304
|
+
# @option values [Hash] :max_age
|
305
|
+
# @option values [Hash] :min_stale
|
306
|
+
# @option values [Hash] :s_max_age
|
307
|
+
#
|
308
|
+
# @return void
|
309
|
+
#
|
159
310
|
# @since 2.0.0
|
160
311
|
# @api public
|
161
312
|
def cache_control(*values)
|
@@ -163,6 +314,28 @@ module Hanami
|
|
163
314
|
headers.merge!(directives.headers)
|
164
315
|
end
|
165
316
|
|
317
|
+
# Sets the `Expires` header and `Cache-Control`/`max-age` directive for the response.
|
318
|
+
#
|
319
|
+
# You can provide an integer number of seconds in the future, or a Time object indicating when
|
320
|
+
# the response should be considered "stale". The remaining arguments are passed to
|
321
|
+
# {#cache_control}.
|
322
|
+
#
|
323
|
+
# @example
|
324
|
+
# # Set Cache-Control directives and Expires
|
325
|
+
# response.expires 900, :public
|
326
|
+
#
|
327
|
+
# # Overwrite Cache-Control directives and Expires
|
328
|
+
# response.expires 300, :private, :no_cache, :no_store
|
329
|
+
#
|
330
|
+
# response.get_header("Expires") # => "Thu, 26 Jun 2014 12:00:00 GMT"
|
331
|
+
# response.get_header("Cache-Control") # => "private, no-cache, no-store max-age=300"
|
332
|
+
#
|
333
|
+
# @param amount [Integer, Time] number of seconds or point in time
|
334
|
+
# @param values [Array<Symbols>] values to map to `Cache-Control` directives via
|
335
|
+
# {#cache_control}
|
336
|
+
#
|
337
|
+
# @return void
|
338
|
+
#
|
166
339
|
# @since 2.0.0
|
167
340
|
# @api public
|
168
341
|
def expires(amount, *values)
|
@@ -170,6 +343,26 @@ module Hanami
|
|
170
343
|
headers.merge!(directives.headers)
|
171
344
|
end
|
172
345
|
|
346
|
+
# Sets the `etag` and/or `last_modified` headers on the response and halts with a `304 Not
|
347
|
+
# Modified` response if the request is still fresh according to the `IfNoneMatch` and
|
348
|
+
# `IfModifiedSince` request headers.
|
349
|
+
#
|
350
|
+
# @example
|
351
|
+
# # Set etag header and halt 304 if request matches IF_NONE_MATCH header
|
352
|
+
# response.fresh etag: some_resource.updated_at.to_i
|
353
|
+
#
|
354
|
+
# # Set last_modified header and halt 304 if request matches IF_MODIFIED_SINCE
|
355
|
+
# response.fresh last_modified: some_resource.updated_at
|
356
|
+
#
|
357
|
+
# # Set etag and last_modified header and halt 304 if request matches IF_MODIFIED_SINCE and IF_NONE_MATCH
|
358
|
+
# response.fresh last_modified: some_resource.updated_at
|
359
|
+
#
|
360
|
+
# @param options [Hash]
|
361
|
+
# @option options [Integer] :etag for testing IfNoneMatch conditions
|
362
|
+
# @option options [Date] :last_modified for testing IfModifiedSince conditions
|
363
|
+
#
|
364
|
+
# @return void
|
365
|
+
#
|
173
366
|
# @since 2.0.0
|
174
367
|
# @api public
|
175
368
|
def fresh(options)
|
@@ -183,7 +376,7 @@ module Hanami
|
|
183
376
|
end
|
184
377
|
|
185
378
|
# @since 2.0.0
|
186
|
-
# @api
|
379
|
+
# @api private
|
187
380
|
def set_format(value) # rubocop:disable Naming/AccessorMethodName
|
188
381
|
@format = value
|
189
382
|
end
|
@@ -209,7 +402,7 @@ module Hanami
|
|
209
402
|
alias_method :to_ary, :to_a
|
210
403
|
|
211
404
|
# @since 2.0.0
|
212
|
-
# @api
|
405
|
+
# @api private
|
213
406
|
def head?
|
214
407
|
env[Action::REQUEST_METHOD] == Action::HEAD
|
215
408
|
end
|
@@ -1,15 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "hanami/action/flash"
|
4
|
-
|
5
3
|
module Hanami
|
6
4
|
class Action
|
7
|
-
# Session
|
5
|
+
# Session support for actions.
|
8
6
|
#
|
9
|
-
#
|
7
|
+
# Not included by default; you should include this module manually to enable session support.
|
8
|
+
# For actions within an Hanami app, this module will be included automatically if sessions are
|
9
|
+
# configured in the app config.
|
10
10
|
#
|
11
|
+
# @api public
|
11
12
|
# @since 0.1.0
|
12
13
|
module Session
|
14
|
+
# @api private
|
15
|
+
# @since 0.1.0
|
13
16
|
def self.included(base)
|
14
17
|
base.class_eval do
|
15
18
|
before { |req, _| req.id }
|
@@ -1,9 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "params"
|
4
4
|
|
5
5
|
module Hanami
|
6
6
|
class Action
|
7
|
+
# Support for validating params when calling actions.
|
8
|
+
#
|
9
|
+
# Included only when hanami-validations (and its dependencies) are bundled.
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
# @since 0.1.0
|
7
13
|
module Validatable
|
8
14
|
# Defines the class name for anonymous params
|
9
15
|
#
|
@@ -34,13 +40,10 @@ module Hanami
|
|
34
40
|
# Once whitelisted, the params are available as an Hash with symbols
|
35
41
|
# as keys.
|
36
42
|
#
|
37
|
-
#
|
38
|
-
#
|
39
43
|
# It accepts an anonymous block where all the params can be listed.
|
40
44
|
# It internally creates an inner class which inherits from
|
41
45
|
# Hanami::Action::Params.
|
42
46
|
#
|
43
|
-
#
|
44
47
|
# Alternatively, it accepts an concrete class that should inherit from
|
45
48
|
# Hanami::Action::Params.
|
46
49
|
#
|
@@ -49,8 +52,6 @@ module Hanami
|
|
49
52
|
#
|
50
53
|
# @return void
|
51
54
|
#
|
52
|
-
# @since 0.3.0
|
53
|
-
#
|
54
55
|
# @see Hanami::Action::Params
|
55
56
|
# @see https://guides.hanamirb.org//validations/overview
|
56
57
|
#
|
@@ -93,6 +94,9 @@ module Hanami
|
|
93
94
|
# req.params[:admin] # => nil
|
94
95
|
# end
|
95
96
|
# end
|
97
|
+
#
|
98
|
+
# @since 0.3.0
|
99
|
+
# @api public
|
96
100
|
def params(klass = nil, &blk)
|
97
101
|
if klass.nil?
|
98
102
|
klass = const_set(PARAMS_CLASS_NAME, Class.new(Params))
|