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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a363029b7df3ddc560e2975862f72548788a2a2910c2d32519f073556b4bfbb2
4
- data.tar.gz: e7d67505e3b0161a656d24e1ff634bd02c11d4fad1e217a1ee482271be6dbbd9
3
+ metadata.gz: '03586fe42cbe2496c8433bb50cf8b176d1edd75ecd2848d89e28e9d6b2cd735e'
4
+ data.tar.gz: 3a5e3a900db6918d9c3f74e49509efb5e33afcf9843ac76aa837fa193f6eb748
5
5
  SHA512:
6
- metadata.gz: 0a9aeb4ea44db1fe6f137dee8f3e5e05bda318a8c53f1f57c30204a0077b5dcfd82aefab569fd7169c2c728de2331fcaa52f1fcdec2e3adb6d4679c38791edf6
7
- data.tar.gz: 726de07b87cd8055da9973ea0365c00bde1f8808a1593ef3830051e196c722f001f6dc13615182f067542c605c9437259d1d36028031867edf3abd8d9612e817
6
+ metadata.gz: de6142be19fc0a9d18e8a27260ab94729d2103f72589e9c36db8226d9cf9bb19b9cec9193e0c7e00659afd5f8aee775abda2af0ed21e1962315d77c6b789bd89
7
+ data.tar.gz: 98927780e4bfd2e31de03b3d2bb01441aad6405743ff68991d80a76c92647191bd929e2a345554cf5146097cf3dd222717451b7f93d326cf0fb49ef5ce7905f8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  Complete, fast and testable actions for Rack
4
4
 
5
+ ## v2.0.0 - 2022-11-22
6
+
7
+ ### Added
8
+
9
+ - [Tim Riley] Use Zeitwerk to autoload the gem
10
+ - [Tim Riley] Introduce `Hanami::Action::Config#formats`. Use `config.actions.formats.add(:json)`. Custom formats can use `config.actions.formats.add(:graphql, ["application/graphql"])`
11
+
12
+ ### Changed
13
+
14
+ - [Tim Riley] Changed `Hanami::Action::Config#format` semantic: it's no longer used to add custom MIME Types, but as a macro to setup the wanted format for action(s)
15
+ - [Tim Riley] Removed `Hanami::Action::Config#default_request_format` and `#default_response_format`, use `#format` for both
16
+ - [Tim Riley] Removed `Hanami::Action::Config#accept`, use `#format`
17
+
18
+ ## v2.0.0.rc1 - 2022-11-08
19
+
20
+ ### Changed
21
+
22
+ - [Tim Riley] Simplify assignment of response format: `response.format = :json` (was `response.format = format(:json)`)
23
+
5
24
  ## v2.0.0.beta4 - 2022-10-24
6
25
 
7
26
  ### Added
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
 
@@ -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
 
@@ -928,12 +928,6 @@ configuration = Hanami::Controller::Configuration.new do |config|
928
928
  #
929
929
  config.format custom: "application/custom"
930
930
 
931
- # Define a fallback format to detect in case of HTTP request with `Accept: */*`
932
- # If not defined here, it will return Rack's default: `application/octet-stream`
933
- # Argument: symbol, it should be already known. defaults to `nil`
934
- #
935
- config.default_request_format = :html
936
-
937
931
  # Define a default format to set as `Content-Type` header for response,
938
932
  # unless otherwise specified.
939
933
  # If not defined here, it will return Rack's default: `application/octet-stream`
@@ -20,9 +20,11 @@ Gem::Specification.new do |spec|
20
20
  spec.metadata["rubygems_mfa_required"] = "true"
21
21
  spec.required_ruby_version = ">= 3.0"
22
22
 
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"
23
+ spec.add_dependency "rack", "~> 2.0"
24
+ spec.add_dependency "hanami-utils", "~> 2.0"
25
+ spec.add_dependency "dry-configurable", "~> 1.0", "< 2"
26
+ spec.add_dependency "dry-core", "~> 1.0"
27
+ spec.add_dependency "zeitwerk", "~> 2.6"
26
28
 
27
29
  spec.add_development_dependency "bundler", ">= 1.6", "< 3"
28
30
  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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/action/cache/directives"
4
-
5
3
  module Hanami
6
4
  class Action
7
5
  module Cache
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/action/cache/cache_control"
4
-
5
3
  module Hanami
6
4
  class Action
7
5
  module Cache
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/action/cache/cache_control"
4
- require "hanami/action/cache/expires"
5
- require "hanami/action/cache/conditional_get"
6
-
7
3
  module Hanami
8
4
  class Action
9
5
  # Cache type API
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/kernel"
4
+ require "dry/core"
5
+
6
+ module Hanami
7
+ class Action
8
+ class Config
9
+ # Action format configuration.
10
+ #
11
+ # @since 2.0.0
12
+ # @api private
13
+ class Formats
14
+ include Dry.Equalizer(:values, :mapping)
15
+
16
+ # Default MIME type to format mapping
17
+ #
18
+ # @since 2.0.0
19
+ # @api private
20
+ DEFAULT_MAPPING = {
21
+ "application/octet-stream" => :all,
22
+ "*/*" => :all
23
+ }.freeze
24
+
25
+ # @since 2.0.0
26
+ # @api private
27
+ attr_reader :mapping
28
+
29
+ # The array of enabled formats.
30
+ #
31
+ # @example
32
+ # config.formats.values = [:html, :json]
33
+ # config.formats.values # => [:html, :json]
34
+ #
35
+ # @since 2.0.0
36
+ # @api public
37
+ attr_reader :values
38
+
39
+ # @since 2.0.0
40
+ # @api private
41
+ def initialize(values: [], mapping: DEFAULT_MAPPING.dup)
42
+ @values = values
43
+ @mapping = mapping
44
+ end
45
+
46
+ # @since 2.0.0
47
+ # @api private
48
+ private def initialize_copy(original) # rubocop:disable Style/AccessModifierDeclarations
49
+ super
50
+ @values = original.values.dup
51
+ @mapping = original.mapping.dup
52
+ end
53
+
54
+ # !@attribute [w] values
55
+ # @since 2.0.0
56
+ # @api public
57
+ def values=(formats)
58
+ @values = formats.map { |f| Utils::Kernel.Symbol(f) }
59
+ end
60
+
61
+ # @overload add(format)
62
+ # Adds and enables a format.
63
+ #
64
+ # @param format [Symbol]
65
+ #
66
+ # @example
67
+ # config.formats.add(:json)
68
+ #
69
+ # @overload add(format, mime_type)
70
+ # Adds a custom format to MIME type mapping and enables the format.
71
+ # Adds a format mapping to a single MIME type.
72
+ #
73
+ # @param format [Symbol]
74
+ # @param mime_type [String]
75
+ #
76
+ # @example
77
+ # config.formats.add(:json, "application/json")
78
+ #
79
+ # @overload add(format, mime_types)
80
+ # Adds a format mapping to multiple MIME types.
81
+ #
82
+ # @param format [Symbol]
83
+ # @param mime_types [Array<String>]
84
+ #
85
+ # @example
86
+ # config.formats.add(:json, ["application/json+scim", "application/json"])
87
+ #
88
+ # @return [self]
89
+ #
90
+ # @since 2.0.0
91
+ # @api public
92
+ def add(format, mime_types = [])
93
+ format = Utils::Kernel.Symbol(format)
94
+
95
+ Array(mime_types).each do |mime_type|
96
+ @mapping[Utils::Kernel.String(mime_type)] = format
97
+ end
98
+
99
+ @values << format unless @values.include?(format)
100
+
101
+ self
102
+ end
103
+
104
+ # @since 2.0.0
105
+ # @api private
106
+ def empty?
107
+ @values.empty?
108
+ end
109
+
110
+ # @since 2.0.0
111
+ # @api private
112
+ def any?
113
+ @values.any?
114
+ end
115
+
116
+ # @since 2.0.0
117
+ # @api private
118
+ def map(&blk)
119
+ @values.map(&blk)
120
+ end
121
+
122
+ # @since 2.0.0
123
+ # @api private
124
+ def mapping=(mappings)
125
+ @mapping = {}
126
+
127
+ mappings.each do |format_name, mime_types|
128
+ Array(mime_types).each do |mime_type|
129
+ add(format_name, mime_type)
130
+ end
131
+ end
132
+ end
133
+
134
+ # Clears any previously added mappings and format values.
135
+ #
136
+ # @return [self]
137
+ #
138
+ # @since 2.0.0
139
+ # @api public
140
+ def clear
141
+ @mapping = DEFAULT_MAPPING.dup
142
+ @values = []
143
+
144
+ self
145
+ end
146
+
147
+ # Retrieve the format name associated with the given MIME Type
148
+ #
149
+ # @param mime_type [String] the MIME Type
150
+ #
151
+ # @return [Symbol,NilClass] the associated format name, if any
152
+ #
153
+ # @example
154
+ # @config.formats.format_for("application/json") # => :json
155
+ #
156
+ # @see #mime_type_for
157
+ #
158
+ # @since 2.0.0
159
+ # @api public
160
+ def format_for(mime_type)
161
+ @mapping[mime_type]
162
+ end
163
+
164
+ # Returns the primary MIME type associated with the given format.
165
+ #
166
+ # @param format [Symbol] the format name
167
+ #
168
+ # @return [String, nil] the associated MIME type, if any
169
+ #
170
+ # @example
171
+ # @config.formats.mime_type_for(:json) # => "application/json"
172
+ #
173
+ # @see #format_for
174
+ #
175
+ # @since 2.0.0
176
+ # @api public
177
+ def mime_type_for(format)
178
+ @mapping.key(format)
179
+ end
180
+
181
+ # Returns an array of all MIME types associated with the given format.
182
+ #
183
+ # Returns an empty array if no such format is configured.
184
+ #
185
+ # @param format [Symbol] the format name
186
+ #
187
+ # @return [Array<String>] the associated MIME types
188
+ #
189
+ # @since 2.0.0
190
+ # @api public
191
+ def mime_types_for(format)
192
+ @mapping.each_with_object([]) { |(mime_type, f), arr| arr << mime_type if format == f }
193
+ end
194
+
195
+ # Returns the default format name
196
+ #
197
+ # @return [Symbol, nil] the default format name, if any
198
+ #
199
+ # @example
200
+ # @config.formats.default # => :json
201
+ #
202
+ # @since 2.0.0
203
+ # @api public
204
+ def default
205
+ @values.first
206
+ end
207
+
208
+ # @since 2.0.0
209
+ # @api private
210
+ def keys
211
+ @mapping.keys
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -1,21 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/configurable"
4
- require_relative "mime"
5
4
 
6
5
  module Hanami
7
6
  class Action
7
+ # Config for `Hanami::Action` classes.
8
+ #
9
+ # @see Hanami::Action.config
10
+ #
11
+ # @api public
12
+ # @since 2.0.0
8
13
  class Config < Dry::Configurable::Config
9
- # Default MIME type to format mapping
10
- #
11
- # @since 0.2.0
12
- # @api private
13
- DEFAULT_FORMATS = {
14
- "application/octet-stream" => :all,
15
- "*/*" => :all,
16
- "text/html" => :html
17
- }.freeze
18
-
19
14
  # Default public directory
20
15
  #
21
16
  # This serves as the root directory for file downloads
@@ -63,121 +58,37 @@ module Hanami
63
58
  .to_h
64
59
  end
65
60
 
66
- # @!attribute [rw] formats
67
- #
68
- # Specifies the MIME type to format mapping
69
- #
70
- # @return [Hash{String=>Symbol}] MIME type strings as keys and format symbols as values
71
- #
72
- # @see format
73
- # @see Hanami::Action::Mime
61
+ # @!attribute [r] formats
62
+ # Returns the format config for the action.
74
63
  #
75
- # @example
76
- # config.formats = {"text/html" => :html}
64
+ # @return [Config::Formats]
77
65
  #
78
- # @since 0.2.0
66
+ # @since 2.0.0
67
+ # @api public
79
68
 
80
- # Registers a MIME type to format mapping
69
+ # Sets the format (or formats) for the action.
81
70
  #
82
- # @param hash [Hash{Symbol=>String}] format symbols as keys and the MIME
83
- # type strings must as values
84
- #
85
- # @return [void]
86
- #
87
- # @see formats
88
- # @see Hanami::Action::Mime
71
+ # To configure custom formats and MIME type mappings, call {Formats#add formats.add} first.
89
72
  #
90
73
  # @example
91
- # config.format html: "text/html"
92
- #
93
- # @since 0.2.0
94
- def format(hash)
95
- symbol, mime_type = *Utils::Kernel.Array(hash)
96
- formats[Utils::Kernel.String(mime_type)] = Utils::Kernel.Symbol(symbol)
97
- end
98
-
99
- # Returns the configured format for the given MIME type
100
- #
101
- # @param mime_type [#to_s,#to_str] A mime type
102
- #
103
- # @return [Symbol,nil] the corresponding format, nil if not found
104
- #
105
- # @see format
106
- #
107
- # @since 0.2.0
108
- # @api private
109
- def format_for(mime_type)
110
- formats[mime_type]
111
- end
112
-
113
- # Returns the configured format's MIME types
114
- #
115
- # @return [Array<String>] the format's MIME types
116
- #
117
- # @see formats=
118
- # @see format
74
+ # config.format :html, :json
119
75
  #
120
- # @since 0.8.0
76
+ # @param formats [Array<Symbol>] the format names
121
77
  #
122
- # @api private
123
- def mime_types
124
- # FIXME: this isn't efficient. speed it up!
125
- ((formats.keys - DEFAULT_FORMATS.keys) +
126
- Hanami::Action::Mime::TYPES.values).freeze
127
- end
128
-
129
- # Returns a MIME type for the given format
130
- #
131
- # @param format [#to_sym] a format
78
+ # @return [Array<Symbol>] the given format names
132
79
  #
133
- # @return [String,nil] the corresponding MIME type, if present
80
+ # @see #formats
134
81
  #
135
- # @since 0.2.0
136
- # @api private
137
- def mime_type_for(format)
138
- formats.key(format)
139
- end
140
-
141
82
  # @since 2.0.0
142
- # @api private
143
- def accepted_mime_types
144
- accepted_formats.any? ? Mime.restrict_mime_types(self) : mime_types
83
+ # @api public
84
+ def format(*formats)
85
+ if formats.empty?
86
+ self.formats.values
87
+ else
88
+ self.formats.values = formats
89
+ end
145
90
  end
146
91
 
147
- # @!attribute [rw] default_request_format
148
- #
149
- # Sets a format as default fallback for all the requests without a strict
150
- # requirement for the MIME type.
151
- #
152
- # The given format must be coercible to a symbol, and be a valid MIME
153
- # type alias. If it isn't, at runtime the framework will raise an
154
- # `Hanami::Controller::UnknownFormatError`.
155
- #
156
- # By default, this value is nil.
157
- #
158
- # @return [Symbol]
159
- #
160
- # @see Hanami::Action::Mime
161
- #
162
- # @since 0.5.0
163
-
164
- # @!attribute [rw] default_response_format
165
- #
166
- # Sets a format to be used for all responses regardless of the request
167
- # type.
168
- #
169
- # The given format must be coercible to a symbol, and be a valid MIME
170
- # type alias. If it isn't, at the runtime the framework will raise an
171
- # `Hanami::Controller::UnknownFormatError`.
172
- #
173
- # By default, this value is nil.
174
- #
175
- # @return [Symbol]
176
- #
177
- # @see Hanami::Action::Mime
178
- #
179
- # @since 0.5.0
180
-
181
92
  # @!attribute [rw] default_charset
182
93
  #
183
94
  # Sets a charset (character set) as default fallback for all the requests
@@ -116,12 +116,6 @@ module Hanami
116
116
  # @api private
117
117
  HTTP_ACCEPT = "HTTP_ACCEPT"
118
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
124
-
125
119
  # The default mime type for an incoming HTTP request
126
120
  #
127
121
  # @since 0.1.0
@@ -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.