hanami-controller 2.0.0.rc1 → 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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +0 -6
- data/hanami-controller.gemspec +4 -2
- data/lib/hanami/action/cache/cache_control.rb +0 -2
- data/lib/hanami/action/cache/expires.rb +0 -2
- data/lib/hanami/action/cache.rb +0 -4
- data/lib/hanami/action/config/formats.rb +216 -0
- data/lib/hanami/action/config.rb +18 -113
- data/lib/hanami/action/errors.rb +8 -1
- data/lib/hanami/action/mime/request_mime_weight.rb +66 -0
- data/lib/hanami/action/mime.rb +177 -220
- data/lib/hanami/action/params.rb +0 -1
- data/lib/hanami/action/request.rb +0 -1
- data/lib/hanami/action/response.rb +10 -10
- data/lib/hanami/action/session.rb +0 -2
- data/lib/hanami/action/validatable.rb +1 -1
- data/lib/hanami/action.rb +39 -56
- data/lib/hanami/controller/version.rb +6 -1
- data/lib/hanami/controller.rb +2 -14
- data/lib/hanami-controller.rb +3 -0
- metadata +38 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '03586fe42cbe2496c8433bb50cf8b176d1edd75ecd2848d89e28e9d6b2cd735e'
|
4
|
+
data.tar.gz: 3a5e3a900db6918d9c3f74e49509efb5e33afcf9843ac76aa837fa193f6eb748
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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`
|
data/hanami-controller.gemspec
CHANGED
@@ -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",
|
24
|
-
spec.add_dependency "hanami-utils", "~> 2.0
|
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"
|
data/lib/hanami/action/cache.rb
CHANGED
@@ -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
|
data/lib/hanami/action/config.rb
CHANGED
@@ -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 [
|
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
|
-
# @
|
82
|
-
# config.formats = {"text/html" => :html}
|
64
|
+
# @return [Config::Formats]
|
83
65
|
#
|
84
|
-
# @since
|
66
|
+
# @since 2.0.0
|
67
|
+
# @api public
|
85
68
|
|
86
|
-
#
|
69
|
+
# Sets the format (or formats) for the action.
|
87
70
|
#
|
88
|
-
#
|
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:
|
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
|
-
# @
|
76
|
+
# @param formats [Array<Symbol>] the format names
|
127
77
|
#
|
128
|
-
# @
|
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
|
-
# @
|
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
|
149
|
-
def
|
150
|
-
|
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
|
data/lib/hanami/action/errors.rb
CHANGED
@@ -21,7 +21,14 @@ module Hanami
|
|
21
21
|
# @since 2.0.0
|
22
22
|
# @api private
|
23
23
|
def initialize(format)
|
24
|
-
|
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
|