hanami-controller 2.0.0.rc1 → 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: 0a5d623c09e8ecb8c267de856196333c5da74e066b55501af2acc706349d0d73
4
- data.tar.gz: '0786076a81b7a11ca79aedba2ff0318f207683e71ba931293dcc1f585a191e0b'
3
+ metadata.gz: '03586fe42cbe2496c8433bb50cf8b176d1edd75ecd2848d89e28e9d6b2cd735e'
4
+ data.tar.gz: 3a5e3a900db6918d9c3f74e49509efb5e33afcf9843ac76aa837fa193f6eb748
5
5
  SHA512:
6
- metadata.gz: 81f1cddefe11892d05ccb63e8649bc9fb6e50f2258eb02503044577e9c0e445c57cbf2318046a4002e3c9b107f9df530463cbeb10460bb3339bb52b747deb0eb
7
- data.tar.gz: eb26f557543e0d345580b9160ec4123def85b4a4ec5a1ad0a17f8a8d15d05dc34f12e13705082e21b95a5cc3095d36677b099a319a1c2472c7180e413f5c5faf
6
+ metadata.gz: de6142be19fc0a9d18e8a27260ab94729d2103f72589e9c36db8226d9cf9bb19b9cec9193e0c7e00659afd5f8aee775abda2af0ed21e1962315d77c6b789bd89
7
+ data.tar.gz: 98927780e4bfd2e31de03b3d2bb01441aad6405743ff68991d80a76c92647191bd929e2a345554cf5146097cf3dd222717451b7f93d326cf0fb49ef5ce7905f8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
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
+
5
18
  ## v2.0.0.rc1 - 2022-11-08
6
19
 
7
20
  ### Changed
data/README.md CHANGED
@@ -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.0.rc1"
23
+ spec.add_dependency "rack", "~> 2.0"
24
+ spec.add_dependency "hanami-utils", "~> 2.0"
25
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"
@@ -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,7 +1,6 @@
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
@@ -12,16 +11,6 @@ module Hanami
12
11
  # @api public
13
12
  # @since 2.0.0
14
13
  class Config < Dry::Configurable::Config
15
- # Default MIME type to format mapping
16
- #
17
- # @since 0.2.0
18
- # @api private
19
- DEFAULT_FORMATS = {
20
- "application/octet-stream" => :all,
21
- "*/*" => :all,
22
- "text/html" => :html
23
- }.freeze
24
-
25
14
  # Default public directory
26
15
  #
27
16
  # This serves as the root directory for file downloads
@@ -69,121 +58,37 @@ module Hanami
69
58
  .to_h
70
59
  end
71
60
 
72
- # @!attribute [rw] formats
73
- #
74
- # Specifies the MIME type to format mapping
75
- #
76
- # @return [Hash{String=>Symbol}] MIME type strings as keys and format symbols as values
77
- #
78
- # @see format
79
- # @see Hanami::Action::Mime
61
+ # @!attribute [r] formats
62
+ # Returns the format config for the action.
80
63
  #
81
- # @example
82
- # config.formats = {"text/html" => :html}
64
+ # @return [Config::Formats]
83
65
  #
84
- # @since 0.2.0
66
+ # @since 2.0.0
67
+ # @api public
85
68
 
86
- # Registers a MIME type to format mapping
69
+ # Sets the format (or formats) for the action.
87
70
  #
88
- # @param hash [Hash{Symbol=>String}] format symbols as keys and the MIME
89
- # type strings must as values
90
- #
91
- # @return [void]
92
- #
93
- # @see formats
94
- # @see Hanami::Action::Mime
71
+ # To configure custom formats and MIME type mappings, call {Formats#add formats.add} first.
95
72
  #
96
73
  # @example
97
- # config.format html: "text/html"
98
- #
99
- # @since 0.2.0
100
- def format(hash)
101
- symbol, mime_type = *Utils::Kernel.Array(hash)
102
- formats[Utils::Kernel.String(mime_type)] = Utils::Kernel.Symbol(symbol)
103
- end
104
-
105
- # Returns the configured format for the given MIME type
106
- #
107
- # @param mime_type [#to_s,#to_str] A mime type
108
- #
109
- # @return [Symbol,nil] the corresponding format, nil if not found
110
- #
111
- # @see format
112
- #
113
- # @since 0.2.0
114
- # @api private
115
- def format_for(mime_type)
116
- formats[mime_type]
117
- end
118
-
119
- # Returns the configured format's MIME types
120
- #
121
- # @return [Array<String>] the format's MIME types
122
- #
123
- # @see formats=
124
- # @see format
74
+ # config.format :html, :json
125
75
  #
126
- # @since 0.8.0
76
+ # @param formats [Array<Symbol>] the format names
127
77
  #
128
- # @api private
129
- def mime_types
130
- # FIXME: this isn't efficient. speed it up!
131
- ((formats.keys - DEFAULT_FORMATS.keys) +
132
- Hanami::Action::Mime::TYPES.values).freeze
133
- end
134
-
135
- # Returns a MIME type for the given format
136
- #
137
- # @param format [#to_sym] a format
78
+ # @return [Array<Symbol>] the given format names
138
79
  #
139
- # @return [String,nil] the corresponding MIME type, if present
80
+ # @see #formats
140
81
  #
141
- # @since 0.2.0
142
- # @api private
143
- def mime_type_for(format)
144
- formats.key(format)
145
- end
146
-
147
82
  # @since 2.0.0
148
- # @api private
149
- def accepted_mime_types
150
- 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
151
90
  end
152
91
 
153
- # @!attribute [rw] default_request_format
154
- #
155
- # Sets a format as default fallback for all the requests without a strict
156
- # requirement for the MIME type.
157
- #
158
- # The given format must be coercible to a symbol, and be a valid MIME
159
- # type alias. If it isn't, at runtime the framework will raise an
160
- # `Hanami::Action::UnknownFormatError`.
161
- #
162
- # By default, this value is nil.
163
- #
164
- # @return [Symbol]
165
- #
166
- # @see Hanami::Action::Mime
167
- #
168
- # @since 0.5.0
169
-
170
- # @!attribute [rw] default_response_format
171
- #
172
- # Sets a format to be used for all responses regardless of the request
173
- # type.
174
- #
175
- # The given format must be coercible to a symbol, and be a valid MIME
176
- # type alias. If it isn't, at the runtime the framework will raise an
177
- # `Hanami::Action::UnknownFormatError`.
178
- #
179
- # By default, this value is nil.
180
- #
181
- # @return [Symbol]
182
- #
183
- # @see Hanami::Action::Mime
184
- #
185
- # @since 0.5.0
186
-
187
92
  # @!attribute [rw] default_charset
188
93
  #
189
94
  # Sets a charset (character set) as default fallback for all the requests
@@ -21,7 +21,14 @@ module Hanami
21
21
  # @since 2.0.0
22
22
  # @api private
23
23
  def initialize(format)
24
- super("Cannot find a corresponding Mime type for '#{format}'. Please configure it with Hanami::Controller::Configuration#format.") # rubocop:disable Layout/LineLength
24
+ msg =
25
+ if format.to_s != "" # rubocop:disable Style/NegatedIfElseCondition
26
+ "Cannot find a corresponding MIME type for format #{format.inspect}. Configure one via `config.formats.add(#{format}: \"MIME_TYPE_HERE\")`." # rubocop:disable Layout/LineLength
27
+ else
28
+ "Cannot find a corresponding MIME type for `nil` format."
29
+ end
30
+
31
+ super(msg)
25
32
  end
26
33
  end
27
34
 
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Action
5
+ module Mime
6
+ # @since 1.0.1
7
+ # @api private
8
+ class RequestMimeWeight
9
+ # @since 2.0.0
10
+ # @api private
11
+ MIME_SEPARATOR = "/"
12
+ private_constant :MIME_SEPARATOR
13
+
14
+ # @since 2.0.0
15
+ # @api private
16
+ MIME_WILDCARD = "*"
17
+ private_constant :MIME_WILDCARD
18
+
19
+ include Comparable
20
+
21
+ # @since 1.0.1
22
+ # @api private
23
+ attr_reader :quality
24
+
25
+ # @since 1.0.1
26
+ # @api private
27
+ attr_reader :index
28
+
29
+ # @since 1.0.1
30
+ # @api private
31
+ attr_reader :mime
32
+
33
+ # @since 1.0.1
34
+ # @api private
35
+ attr_reader :format
36
+
37
+ # @since 1.0.1
38
+ # @api private
39
+ attr_reader :priority
40
+
41
+ # @since 1.0.1
42
+ # @api private
43
+ def initialize(mime, quality, index, format = mime)
44
+ @quality, @index, @format = quality, index, format
45
+ calculate_priority(mime)
46
+ end
47
+
48
+ # @since 1.0.1
49
+ # @api private
50
+ def <=>(other)
51
+ return priority <=> other.priority unless priority == other.priority
52
+
53
+ other.index <=> index
54
+ end
55
+
56
+ private
57
+
58
+ # @since 1.0.1
59
+ # @api private
60
+ def calculate_priority(mime)
61
+ @priority ||= (mime.split(MIME_SEPARATOR, 2).count(MIME_WILDCARD) * -10) + quality
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end