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.
@@ -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
- # just one HTTP request or redirect.
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
- # This implementation is derived from Roda's FlashHash, also released under
12
- # the MIT Licence:
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
- # Initializes a new flash instance
33
+ # Returns a new flash object.
34
34
  #
35
- # @param hash [Hash, nil] the flash hash for the current request. nil will become an empty hash.
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
- # @return [Hash] The flash hash for the current request
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
- # @param block [Proc]
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 a new array with the results of running block once for every
86
- # element in the current hash
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
@@ -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]
@@ -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(configuration, request, accepted_mime_types)
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(configuration) || default_content_type(configuration) || Action::DEFAULT_CONTENT_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(configuration)
105
- format_to_mime_type(configuration.default_response_format, configuration)
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(configuration)
111
- format_to_mime_type(configuration.default_request_format, configuration)
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, configuration)
117
+ def self.format_to_mime_type(format, config)
117
118
  return if format.nil?
118
119
 
119
- configuration.mime_type_for(format) ||
120
- TYPES.fetch(format) { raise Hanami::Controller::UnknownFormatError.new(format) }
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", configuration) #=> :html
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, configuration)
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
- configuration.format_for(ct) || format_for(ct)
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(configuration, [:json]) #=> ["application/json"]
163
+ # restrict_mime_types(config, [:json]) #=> ["application/json"]
150
164
  #
151
165
  # @return [Array<String>, nil]
152
166
  #
153
- # @raise [Hanami::Controller::UnknownFormatError] if the format is invalid
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(configuration, accepted_formats)
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, configuration)
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 & configuration.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
- # Use for checking the Content-Type header to make sure is valid based
172
- # on the accepted_mime_types
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 no Content-Type is sent in the request it will check the default_request_format
189
+ # If any of these conditions are not met, then the request is acceptable and the method
190
+ # returns without yielding.
175
191
  #
176
- # @return [TrueClass, FalseClass]
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.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
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
- !accepted_mime_types.find { |mt| ::Rack::Mime.match?(mt, mime_type) }.nil?
204
+ yield
186
205
  end
187
206
 
188
- # Use for setting the content_type and charset if the response
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 Hanami::Action::Mime#call
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(configuration, request, accepted_mime_types)
197
- charset = self.charset(configuration.default_charset)
198
- content_type = self.content_type(configuration, request, accepted_mime_types)
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
@@ -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
@@ -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 "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"
8
+ require_relative "errors"
7
9
 
8
10
  module Hanami
9
11
  class Action
10
- # An HTTP request based on top of Rack::Request.
11
- # This guarantees backwards compatibility with with Rack.
12
+ # The HTTP request for an action, given to {Action#handle}.
12
13
  #
13
- # @since 0.3.1
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
- def initialize(env, params)
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