hanami-controller 2.0.0.beta4 → 2.0.0

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: 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.