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 +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +4 -4
- data/hanami-controller.gemspec +2 -2
- data/lib/hanami/action/base_params.rb +39 -16
- data/lib/hanami/action/config.rb +268 -0
- data/lib/hanami/action/constants.rb +5 -17
- data/lib/hanami/action/csrf_protection.rb +2 -7
- data/lib/hanami/action/errors.rb +69 -0
- data/lib/hanami/action/flash.rb +29 -22
- data/lib/hanami/action/halt.rb +3 -1
- data/lib/hanami/action/mime.rb +81 -35
- data/lib/hanami/action/params.rb +5 -12
- data/lib/hanami/action/rack/file.rb +6 -2
- data/lib/hanami/action/request.rb +67 -5
- data/lib/hanami/action/response.rb +221 -30
- data/lib/hanami/action/session.rb +11 -2
- data/lib/hanami/action/validatable.rb +9 -5
- data/lib/hanami/action.rb +181 -188
- data/lib/hanami/controller/version.rb +2 -4
- data/lib/hanami/controller.rb +0 -16
- data/lib/hanami/http/status.rb +2 -0
- metadata +12 -12
- data/lib/hanami/action/configuration.rb +0 -436
- data/lib/hanami/controller/error.rb +0 -9
data/lib/hanami/action/flash.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# The Hanami::Action::Flash implementation is derived from Roda's FlashHash, also released under the
|
4
|
+
# MIT Licence:
|
5
|
+
#
|
6
|
+
# Copyright (c) 2014-2020 Jeremy Evans
|
7
|
+
# Copyright (c) 2010-2014 Michel Martens, Damian Janowski and Cyril David
|
8
|
+
# Copyright (c) 2008-2009 Christian Neukirchen
|
9
|
+
|
3
10
|
module Hanami
|
4
11
|
class Action
|
5
|
-
# A container to transport data with the HTTP session, with a lifespan of
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# Behaves like a hash, returning entries for the current request, except for
|
9
|
-
# {#[]=}, which updates the hash for the next request.
|
12
|
+
# A container to transport data with the HTTP session, with a lifespan of just one HTTP request
|
13
|
+
# or redirect.
|
10
14
|
#
|
11
|
-
#
|
12
|
-
# the
|
13
|
-
#
|
14
|
-
# Copyright (c) 2014-2020 Jeremy Evans
|
15
|
-
# Copyright (c) 2010-2014 Michel Martens, Damian Janowski and Cyril David
|
16
|
-
# Copyright (c) 2008-2009 Christian Neukirchen
|
15
|
+
# Behaves like a hash, returning entries for the current request, except for {#[]=}, which
|
16
|
+
# updates the hash for the next request.
|
17
17
|
#
|
18
18
|
# @since 0.3.0
|
19
19
|
# @api public
|
@@ -30,9 +30,9 @@ module Hanami
|
|
30
30
|
# @api public
|
31
31
|
attr_reader :next
|
32
32
|
|
33
|
-
#
|
33
|
+
# Returns a new flash object.
|
34
34
|
#
|
35
|
-
# @param hash [Hash, nil] the flash hash for the current request
|
35
|
+
# @param hash [Hash, nil] the flash hash for the current request; `nil` will become an empty hash.
|
36
36
|
#
|
37
37
|
# @since 0.3.0
|
38
38
|
# @api public
|
@@ -41,7 +41,9 @@ module Hanami
|
|
41
41
|
@next = {}
|
42
42
|
end
|
43
43
|
|
44
|
-
#
|
44
|
+
# Returns the flash hash for the current request.
|
45
|
+
#
|
46
|
+
# @return [Hash] the flash hash for the current request
|
45
47
|
#
|
46
48
|
# @since 2.0.0
|
47
49
|
# @api public
|
@@ -49,7 +51,7 @@ module Hanami
|
|
49
51
|
@flash
|
50
52
|
end
|
51
53
|
|
52
|
-
# Returns the value for the given key in the current hash
|
54
|
+
# Returns the value for the given key in the current hash.
|
53
55
|
#
|
54
56
|
# @param key [Object] the key
|
55
57
|
#
|
@@ -61,7 +63,7 @@ module Hanami
|
|
61
63
|
@flash[key]
|
62
64
|
end
|
63
65
|
|
64
|
-
# Updates the next hash with the given key and value
|
66
|
+
# Updates the next hash with the given key and value.
|
65
67
|
#
|
66
68
|
# @param key [Object] the key
|
67
69
|
# @param value [Object] the value
|
@@ -72,9 +74,12 @@ module Hanami
|
|
72
74
|
@next[key] = value
|
73
75
|
end
|
74
76
|
|
75
|
-
# Calls the given block once for each element in the current hash
|
77
|
+
# Calls the given block once for each element in the current hash.
|
76
78
|
#
|
77
|
-
# @
|
79
|
+
# @yieldparam element [Array<(Object, Object)>] array containing the key and value from the
|
80
|
+
# hash
|
81
|
+
#
|
82
|
+
# @return [now]
|
78
83
|
#
|
79
84
|
# @since 1.2.0
|
80
85
|
# @api public
|
@@ -82,10 +87,12 @@ module Hanami
|
|
82
87
|
@flash.each(&block)
|
83
88
|
end
|
84
89
|
|
85
|
-
# Returns
|
86
|
-
#
|
90
|
+
# Returns an array of objects returned by the block, called once for each element in the
|
91
|
+
# current hash.
|
92
|
+
#
|
93
|
+
# @yieldparam element [Array<(Object, Object)>] array containing the key and value from the
|
94
|
+
# hash
|
87
95
|
#
|
88
|
-
# @param block [Proc]
|
89
96
|
# @return [Array]
|
90
97
|
#
|
91
98
|
# @since 1.2.0
|
@@ -114,7 +121,7 @@ module Hanami
|
|
114
121
|
@flash.key?(key)
|
115
122
|
end
|
116
123
|
|
117
|
-
# Removes entries from the next hash
|
124
|
+
# Removes entries from the next hash.
|
118
125
|
#
|
119
126
|
# @overload discard(key)
|
120
127
|
# Removes the given key from the next hash
|
data/lib/hanami/action/halt.rb
CHANGED
@@ -4,9 +4,11 @@ require "hanami/http/status"
|
|
4
4
|
|
5
5
|
module Hanami
|
6
6
|
class Action
|
7
|
+
# @api private
|
8
|
+
# @since 2.0.0
|
7
9
|
module Halt
|
8
|
-
# @since 2.0.0
|
9
10
|
# @api private
|
11
|
+
# @since 2.0.0
|
10
12
|
def self.call(status, body = nil)
|
11
13
|
body ||= Http::Status.message_for(status)
|
12
14
|
throw :halt, [status, body]
|
data/lib/hanami/action/mime.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "hanami/utils"
|
4
4
|
require "rack/utils"
|
5
5
|
require "rack/mime"
|
6
|
+
require_relative "errors"
|
6
7
|
|
7
8
|
module Hanami
|
8
9
|
class Action
|
@@ -84,13 +85,13 @@ module Hanami
|
|
84
85
|
#
|
85
86
|
# @since 2.0.0
|
86
87
|
# @api private
|
87
|
-
def self.content_type(
|
88
|
+
def self.content_type(config, request, accepted_mime_types)
|
88
89
|
if request.accept_header?
|
89
90
|
type = best_q_match(request.accept, accepted_mime_types)
|
90
91
|
return type if type
|
91
92
|
end
|
92
93
|
|
93
|
-
default_response_type(
|
94
|
+
default_response_type(config) || default_content_type(config) || Action::DEFAULT_CONTENT_TYPE
|
94
95
|
end
|
95
96
|
|
96
97
|
# @since 2.0.0
|
@@ -101,23 +102,23 @@ module Hanami
|
|
101
102
|
|
102
103
|
# @since 2.0.0
|
103
104
|
# @api private
|
104
|
-
def self.default_response_type(
|
105
|
-
format_to_mime_type(
|
105
|
+
def self.default_response_type(config)
|
106
|
+
format_to_mime_type(config.default_response_format, config)
|
106
107
|
end
|
107
108
|
|
108
109
|
# @since 2.0.0
|
109
110
|
# @api private
|
110
|
-
def self.default_content_type(
|
111
|
-
format_to_mime_type(
|
111
|
+
def self.default_content_type(config)
|
112
|
+
format_to_mime_type(config.default_request_format, config)
|
112
113
|
end
|
113
114
|
|
114
115
|
# @since 2.0.0
|
115
116
|
# @api private
|
116
|
-
def self.format_to_mime_type(format,
|
117
|
+
def self.format_to_mime_type(format, config)
|
117
118
|
return if format.nil?
|
118
119
|
|
119
|
-
|
120
|
-
TYPES.fetch(format) { raise Hanami::
|
120
|
+
config.mime_type_for(format) ||
|
121
|
+
TYPES.fetch(format) { raise Hanami::Action::UnknownFormatError.new(format) }
|
121
122
|
end
|
122
123
|
|
123
124
|
# Transforms MIME Types to symbol
|
@@ -125,17 +126,17 @@ module Hanami
|
|
125
126
|
#
|
126
127
|
# @see Hanami::Action::Mime#finish
|
127
128
|
# @example
|
128
|
-
# detect_format("text/html; charset=utf-8",
|
129
|
+
# detect_format("text/html; charset=utf-8", config) #=> :html
|
129
130
|
#
|
130
131
|
# @return [Symbol, nil]
|
131
132
|
#
|
132
133
|
# @since 2.0.0
|
133
134
|
# @api private
|
134
|
-
def self.detect_format(content_type,
|
135
|
+
def self.detect_format(content_type, config)
|
135
136
|
return if content_type.nil?
|
136
137
|
|
137
138
|
ct = content_type.split(";").first
|
138
|
-
|
139
|
+
config.format_for(ct) || format_for(ct)
|
139
140
|
end
|
140
141
|
|
141
142
|
# @since 2.0.0
|
@@ -144,63 +145,108 @@ module Hanami
|
|
144
145
|
TYPES.key(content_type)
|
145
146
|
end
|
146
147
|
|
148
|
+
# @since 2.0.0
|
149
|
+
# @api private
|
150
|
+
def self.detect_format_and_content_type(value, config)
|
151
|
+
case value
|
152
|
+
when Symbol
|
153
|
+
[value, format_to_mime_type(value, config)]
|
154
|
+
when String
|
155
|
+
[detect_format(value, config), value]
|
156
|
+
else
|
157
|
+
raise UnknownFormatError.new(value)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
147
161
|
# Transforms symbols to MIME Types
|
148
162
|
# @example
|
149
|
-
# restrict_mime_types(
|
163
|
+
# restrict_mime_types(config, [:json]) #=> ["application/json"]
|
150
164
|
#
|
151
165
|
# @return [Array<String>, nil]
|
152
166
|
#
|
153
|
-
# @raise [Hanami::
|
167
|
+
# @raise [Hanami::Action::UnknownFormatError] if the format is invalid
|
154
168
|
#
|
155
169
|
# @since 2.0.0
|
156
170
|
# @api private
|
157
|
-
def self.restrict_mime_types(
|
158
|
-
return if accepted_formats.empty?
|
171
|
+
def self.restrict_mime_types(config)
|
172
|
+
return if config.accepted_formats.empty?
|
159
173
|
|
160
|
-
mime_types = accepted_formats.map do |format|
|
161
|
-
format_to_mime_type(format,
|
174
|
+
mime_types = config.accepted_formats.map do |format|
|
175
|
+
format_to_mime_type(format, config)
|
162
176
|
end
|
163
177
|
|
164
|
-
accepted_mime_types = mime_types &
|
178
|
+
accepted_mime_types = mime_types & config.mime_types
|
165
179
|
|
166
180
|
return if accepted_mime_types.empty?
|
167
181
|
|
168
182
|
accepted_mime_types
|
169
183
|
end
|
170
184
|
|
171
|
-
#
|
172
|
-
#
|
185
|
+
# Yields if an action is configured with `accepted_formats`, the request has an `Accept`
|
186
|
+
# header, and none of the Accept types matches the accepted formats. The given block is
|
187
|
+
# expected to halt the request handling.
|
173
188
|
#
|
174
|
-
# If
|
189
|
+
# If any of these conditions are not met, then the request is acceptable and the method
|
190
|
+
# returns without yielding.
|
175
191
|
#
|
176
|
-
# @
|
192
|
+
# @see Action#enforce_accepted_mime_types
|
193
|
+
# @see Action.accept
|
194
|
+
# @see Config#accepted_formats
|
177
195
|
#
|
178
196
|
# @since 2.0.0
|
179
197
|
# @api private
|
180
|
-
def self.
|
181
|
-
|
182
|
-
|
183
|
-
|
198
|
+
def self.enforce_accept(request, config)
|
199
|
+
return unless request.accept_header?
|
200
|
+
|
201
|
+
accept_types = ::Rack::Utils.q_values(request.accept).map(&:first)
|
202
|
+
return if accept_types.any? { |mime_type| accepted_mime_type?(mime_type, config) }
|
184
203
|
|
185
|
-
|
204
|
+
yield
|
186
205
|
end
|
187
206
|
|
188
|
-
#
|
207
|
+
# Yields if an action is configured with `accepted_formats`, the request has a `Content-Type`
|
208
|
+
# header (or a `default_requst_format` is configured), and the content type does not match the
|
209
|
+
# accepted formats. The given block is expected to halt the request handling.
|
210
|
+
#
|
211
|
+
# If any of these conditions are not met, then the request is acceptable and the method
|
212
|
+
# returns without yielding.
|
189
213
|
#
|
190
|
-
# @see
|
214
|
+
# @see Action#enforce_accepted_mime_types
|
215
|
+
# @see Action.accept
|
216
|
+
# @see Config#accepted_formats
|
217
|
+
#
|
218
|
+
# @since 2.0.0
|
219
|
+
# @api private
|
220
|
+
def self.enforce_content_type(request, config)
|
221
|
+
content_type = request.content_type || default_content_type(config)
|
222
|
+
|
223
|
+
return if content_type.nil?
|
224
|
+
|
225
|
+
return if accepted_mime_type?(content_type, config)
|
226
|
+
|
227
|
+
yield
|
228
|
+
end
|
229
|
+
|
230
|
+
# @since 2.0.0
|
231
|
+
# @api private
|
232
|
+
def self.accepted_mime_type?(mime_type, config)
|
233
|
+
config.accepted_mime_types.any? { |accepted_mime_type|
|
234
|
+
::Rack::Mime.match?(accepted_mime_type, mime_type)
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
# Use for setting the content_type and charset if the response
|
191
239
|
#
|
192
240
|
# @return [String]
|
193
241
|
#
|
194
242
|
# @since 2.0.0
|
195
243
|
# @api private
|
196
|
-
def self.calculate_content_type_with_charset(
|
197
|
-
charset
|
198
|
-
content_type = self.content_type(
|
244
|
+
def self.calculate_content_type_with_charset(config, request, accepted_mime_types)
|
245
|
+
charset = self.charset(config.default_charset)
|
246
|
+
content_type = self.content_type(config, request, accepted_mime_types)
|
199
247
|
content_type_with_charset(content_type, charset)
|
200
248
|
end
|
201
249
|
|
202
|
-
# private
|
203
|
-
|
204
250
|
# Patched version of <tt>Rack::Utils.best_q_match</tt>.
|
205
251
|
#
|
206
252
|
# @since 2.0.0
|
data/lib/hanami/action/params.rb
CHANGED
@@ -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
|
-
|
164
|
-
@params =
|
165
|
-
@errors = Errors.new(
|
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
|
@@ -4,13 +4,17 @@ require "rack/file"
|
|
4
4
|
|
5
5
|
module Hanami
|
6
6
|
class Action
|
7
|
+
# Rack extensions for actions.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
# @since 0.4.3
|
7
11
|
module Rack
|
8
12
|
# File to be sent
|
9
13
|
#
|
14
|
+
# @see Hanami::Action::Response#send_file
|
15
|
+
#
|
10
16
|
# @since 0.4.3
|
11
17
|
# @api private
|
12
|
-
#
|
13
|
-
# @see Hanami::Action::Rack#send_file
|
14
18
|
class File
|
15
19
|
# @param path [String,Pathname] file path
|
16
20
|
#
|
@@ -1,37 +1,99 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "hanami/action/flash"
|
4
4
|
require "rack/mime"
|
5
5
|
require "rack/request"
|
6
|
+
require "rack/utils"
|
6
7
|
require "securerandom"
|
8
|
+
require_relative "errors"
|
7
9
|
|
8
10
|
module Hanami
|
9
11
|
class Action
|
10
|
-
#
|
11
|
-
# This guarantees backwards compatibility with with Rack.
|
12
|
+
# The HTTP request for an action, given to {Action#handle}.
|
12
13
|
#
|
13
|
-
#
|
14
|
+
# Inherits from `Rack::Request`, providing compatibility with Rack functionality.
|
14
15
|
#
|
15
16
|
# @see http://www.rubydoc.info/gems/rack/Rack/Request
|
17
|
+
#
|
18
|
+
# @since 0.3.1
|
16
19
|
class Request < ::Rack::Request
|
20
|
+
# Returns the request's params.
|
21
|
+
#
|
22
|
+
# For an action with {Validatable} included, this will be a {Params} instance, otherwise a
|
23
|
+
# {BaseParams}.
|
24
|
+
#
|
25
|
+
# @return [BaseParams,Params]
|
26
|
+
#
|
27
|
+
# @since 2.0.0
|
28
|
+
# @api public
|
17
29
|
attr_reader :params
|
18
30
|
|
19
|
-
|
31
|
+
# @since 2.0.0
|
32
|
+
# @api private
|
33
|
+
def initialize(env:, params:, sessions_enabled: false)
|
20
34
|
super(env)
|
35
|
+
|
21
36
|
@params = params
|
37
|
+
@sessions_enabled = sessions_enabled
|
22
38
|
end
|
23
39
|
|
40
|
+
# Returns the request's ID
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
#
|
44
|
+
# @since 2.0.0
|
45
|
+
# @api public
|
24
46
|
def id
|
25
47
|
# FIXME: make this number configurable and document the probabilities of clashes
|
26
48
|
@id ||= @env[Action::REQUEST_ID] = SecureRandom.hex(Action::DEFAULT_ID_LENGTH)
|
27
49
|
end
|
28
50
|
|
51
|
+
# Returns the session for the request.
|
52
|
+
#
|
53
|
+
# @return [Hash] the session object
|
54
|
+
#
|
55
|
+
# @raise [MissingSessionError] if sessions are not enabled
|
56
|
+
#
|
57
|
+
# @see Response#session
|
58
|
+
#
|
59
|
+
# @since 2.0.0
|
60
|
+
# @api public
|
61
|
+
def session
|
62
|
+
unless @sessions_enabled
|
63
|
+
raise Hanami::Action::MissingSessionError.new("Hanami::Action::Request#session")
|
64
|
+
end
|
65
|
+
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the flash for the request.
|
70
|
+
#
|
71
|
+
# @return [Flash]
|
72
|
+
#
|
73
|
+
# @raise [MissingSessionError] if sessions are not enabled
|
74
|
+
#
|
75
|
+
# @see Response#flash
|
76
|
+
#
|
77
|
+
# @since 2.0.0
|
78
|
+
# @api public
|
79
|
+
def flash
|
80
|
+
unless @sessions_enabled
|
81
|
+
raise Hanami::Action::MissingSessionError.new("Hanami::Action::Request#flash")
|
82
|
+
end
|
83
|
+
|
84
|
+
@flash ||= Flash.new(session[Flash::KEY])
|
85
|
+
end
|
86
|
+
|
87
|
+
# @since 2.0.0
|
88
|
+
# @api private
|
29
89
|
def accept?(mime_type)
|
30
90
|
!!::Rack::Utils.q_values(accept).find do |mime, _|
|
31
91
|
::Rack::Mime.match?(mime_type, mime)
|
32
92
|
end
|
33
93
|
end
|
34
94
|
|
95
|
+
# @since 2.0.0
|
96
|
+
# @api private
|
35
97
|
def accept_header?
|
36
98
|
accept != Action::DEFAULT_ACCEPT
|
37
99
|
end
|