hanami-action 3.0.0.rc1 → 3.0.1
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 +68 -7
- data/lib/hanami/action/body_parser.rb +9 -23
- data/lib/hanami/action/cache/directives.rb +26 -5
- data/lib/hanami/action/config/formats.rb +1 -1
- data/lib/hanami/action/constants.rb +15 -9
- data/lib/hanami/action/errors.rb +1 -1
- data/lib/hanami/action/response.rb +21 -10
- data/lib/hanami/action/version.rb +1 -1
- data/lib/hanami/action.rb +0 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ca2d8098c5bfeaaf4d1422c04eb56980cda8bc806b03f327f4ecf19d82a7ff3
|
|
4
|
+
data.tar.gz: f9f4e24aa1bac023962e562b1dcba5c232dfa3c7dc74610c983108b3f7eaa0f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5377fc1b6c6cf1c5b1be0832ba55e388cf47d8ad07dc3e4a594a506a080c32e641f1cd01685a370b9a9dbab61143e6ecac8b96646ccb77c821da57e43e9e8478
|
|
7
|
+
data.tar.gz: 50b96bbc3419ca3727708944e4f7d5e35b8de9d81ecb340ada4162c72d15114b3330cc22f03990f73bb3e4757f16b25283c93b7b48a43906baa2000de6859c37
|
data/CHANGELOG.md
CHANGED
|
@@ -9,26 +9,87 @@ and this project adheres to [Break Versioning](https://www.taoensso.com/break-ve
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Support additional `Cache-Control` directives in `response.cache_control`: `:immutable`, `:must_understand`, `stale_while_revalidate:` (requires a duration) and `stale_if_error:` (requires a duration). (@timriley in #522)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `response.cache_control` raises `ArgumentError` for unknown directives instead of silently dropping them. (@timriley in #522)
|
|
17
|
+
|
|
18
|
+
### Deprecated
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
### Security
|
|
25
|
+
|
|
26
|
+
[Unreleased]: https://github.com/hanami/hanami-action/compare/v3.0.1...HEAD
|
|
27
|
+
|
|
28
|
+
## [3.0.1] - 2026-07-03
|
|
29
|
+
|
|
30
|
+
Adding an improvement that was originally intended for 3.0.0.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Support additional `Cache-Control` directives in `response.cache_control`: `:immutable`, `:must_understand`, `stale_while_revalidate:` (requires a duration) and `stale_if_error:` (requires a duration). (@timriley in #522)
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- `response.cache_control` raises `ArgumentError` for unknown directives instead of silently dropping them. (@timriley in #522)
|
|
39
|
+
|
|
40
|
+
[3.0.1]: https://github.com/hanami/hanami-action/compare/v3.0.0...v3.0.1
|
|
41
|
+
|
|
42
|
+
## [3.0.0] - 2026-06-30
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- Parse request bodies based on formats config. (@timriley in #500, #503, #511, #519, @cllns in #508)
|
|
13
47
|
|
|
14
48
|
Parsers for multipart form bodies and JSON are included by default. These require `formats.accept :html` and `formats.accept :json` respectively. When no formats are configured, multipart form bodies are parsed if found.
|
|
15
49
|
|
|
16
50
|
Custom parsers may be registered via e.g. `formats.register(:custom, "application/custom", parser: ->(body, env) { ... })` or directly via `formats.body_parsers["application/custom"] = parser`.
|
|
17
51
|
|
|
18
|
-
Parsed body
|
|
52
|
+
Parsed body params are symbolized as part of becoming the `Hanami::Action::Params` instance. Non-hash bodies are wrapped in `{"_" => parsed_body}`.
|
|
53
|
+
- Support additional `Cache-Control` directives in `response.cache_control`: `:immutable`, `:must_understand`, `stale_while_revalidate:` and `stale_if_error:`. (@timriley in #522)
|
|
19
54
|
- Full JRuby support. (@katafrakt in #498)
|
|
20
55
|
|
|
21
56
|
### Changed
|
|
22
57
|
|
|
23
|
-
|
|
58
|
+
- Renamed gem from hanami-controller to hanami-action. You should now `require "hanami-action"` or `require "hanami/action"`. (@timriley in #507, @cllns in df365b5)
|
|
59
|
+
- Check for the dry-validation gem (instead of hanami-validations, which is now retired) before loading `Action.params` and `Action.contract` support. (@timriley in #505)
|
|
60
|
+
- Raise `ArgumentError` from `response.cache_control` for unknown directives, instead of silently dropping them. (@timriley in #522)
|
|
61
|
+
- Cache the action's resolved configuration as a frozen `Data` snapshot (via dry-configurable's `#to_data`) at initialization, avoiding repeated config lookups on the request hot path for improved memory usage and speed. Required bumping `dry-configurable` to `~> 1.4`. (@cllns in #512)
|
|
62
|
+
|
|
63
|
+
**Possibly breaking:** the action's config is now finalized (frozen) when the action is initialized, so mutating it from instance code is no longer possible. This was only ever an undocumented side-effect of implementation, not a supported pattern.
|
|
64
|
+
- Cut per-request allocations on the `Hanami::Action#call` hot path, roughly halving allocations and increasing throughput ~1.5–2.5x per action invocation. A minimal action drops from 51 to 16 allocations (69% fewer, 2.45x faster); an action with formats negotiating an `Accept` header drops from 95 to 61 allocations (36% fewer, 1.55x faster). (@cllns in #514)
|
|
65
|
+
- Require Ruby 3.3 or newer.
|
|
24
66
|
|
|
25
67
|
### Removed
|
|
26
68
|
|
|
27
|
-
|
|
69
|
+
- Removed deprecated format config methods: `Action.format`, `config.format`, `config.formats.add`, `config.formats.values`. (@timriley in #504)
|
|
70
|
+
- Removed `Hanami::Action::Params.params` and support for defining a contract by subclassing `Hanami::Action::Params`. If you are subclassing `Hanami::Action::Params`, take your params block and move it into a `Dry::Validation::Contract` subclass. Then pass this contract class to `Hanami::Action.params` or `Hanami::Action.contract`. (@timriley in #513)
|
|
28
71
|
|
|
29
|
-
|
|
72
|
+
```ruby
|
|
73
|
+
# Before
|
|
74
|
+
# class SignupParams < Hanami::Action::Params
|
|
75
|
+
# params do
|
|
76
|
+
# required(:email).filled(:str?)
|
|
77
|
+
# end
|
|
78
|
+
# end
|
|
79
|
+
|
|
80
|
+
# After
|
|
81
|
+
class SignupContract < Dry::Validation::Contract
|
|
82
|
+
params do
|
|
83
|
+
required(:email).filled(:str?)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class Signup < Hanami::Action
|
|
88
|
+
params SignupParams
|
|
89
|
+
end
|
|
90
|
+
```
|
|
30
91
|
|
|
31
|
-
[
|
|
92
|
+
[3.0.0]: https://github.com/hanami/hanami-action/compare/v2.3.2...v3.0.0
|
|
32
93
|
|
|
33
94
|
## [3.0.0.rc1] - 2026-06-16
|
|
34
95
|
|
|
@@ -50,7 +111,7 @@ and this project adheres to [Break Versioning](https://www.taoensso.com/break-ve
|
|
|
50
111
|
- Cache the action's resolved configuration as a frozen `Data` snapshot (via dry-configurable's `#to_data`) at initialization, avoiding repeated config lookups on the request hot path for improved memory usage and speed. Required bumping `dry-configurable` to `~> 1.4`. (@cllns in #512)
|
|
51
112
|
|
|
52
113
|
**Possibly breaking:** the action's config is now finalized (frozen) when the action is initialized, so mutating it from instance code is no longer possible. This was only ever an undocumented side-effect of implementation, not a supported pattern.
|
|
53
|
-
- Cut per-request allocations on the `Hanami::Action#call` hot path, roughly halving allocations and increasing throughput ~1.5–2.
|
|
114
|
+
- Cut per-request allocations on the `Hanami::Action#call` hot path, roughly halving allocations and increasing throughput ~1.5–2.5x per action invocation. A minimal action drops from 51 to 16 allocations (69% fewer, 2.45x faster); an action with formats negotiating an `Accept` header drops from 95 to 61 allocations (36% fewer, 1.55x faster). (@cllns in #514)
|
|
54
115
|
- Require Ruby 3.3 or newer.
|
|
55
116
|
|
|
56
117
|
### Removed
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rack"
|
|
4
|
-
require "hanami/utils/hash"
|
|
5
4
|
|
|
6
5
|
module Hanami
|
|
7
6
|
class Action
|
|
@@ -9,7 +8,7 @@ module Hanami
|
|
|
9
8
|
#
|
|
10
9
|
# @api private
|
|
11
10
|
module BodyParser
|
|
12
|
-
FALLBACK_KEY =
|
|
11
|
+
FALLBACK_KEY = "_"
|
|
13
12
|
|
|
14
13
|
class << self
|
|
15
14
|
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
@@ -25,7 +24,7 @@ module Hanami
|
|
|
25
24
|
if env.key?(ROUTER_PARSED_BODY)
|
|
26
25
|
parsed = env[ROUTER_PARSED_BODY]
|
|
27
26
|
env[ACTION_PARSED_BODY] = parsed
|
|
28
|
-
env[ACTION_BODY_PARAMS] =
|
|
27
|
+
env[ACTION_BODY_PARAMS] = body_params(parsed)
|
|
29
28
|
return
|
|
30
29
|
end
|
|
31
30
|
|
|
@@ -59,7 +58,7 @@ module Hanami
|
|
|
59
58
|
parsed = parser.call(body, env)
|
|
60
59
|
|
|
61
60
|
env[ACTION_PARSED_BODY] = parsed
|
|
62
|
-
env[ACTION_BODY_PARAMS] =
|
|
61
|
+
env[ACTION_BODY_PARAMS] = body_params(parsed)
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
@@ -83,25 +82,12 @@ module Hanami
|
|
|
83
82
|
body
|
|
84
83
|
end
|
|
85
84
|
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Recursively symbolizes hash keys within any structure (arrays or hashes).
|
|
96
|
-
def deep_symbolize(value)
|
|
97
|
-
case value
|
|
98
|
-
when ::Hash
|
|
99
|
-
Utils::Hash.deep_symbolize(value)
|
|
100
|
-
when ::Array
|
|
101
|
-
value.map { deep_symbolize(_1) }
|
|
102
|
-
else
|
|
103
|
-
value
|
|
104
|
-
end
|
|
85
|
+
# Wraps a parsed body into a hash suitable for merging into params.
|
|
86
|
+
#
|
|
87
|
+
# Hash bodies are returned as-is; non-hash bodies (e.g. JSON arrays) are wrapped under
|
|
88
|
+
# {FALLBACK_KEY}. Keys are left as strings. Symbolization happens in {Params}.
|
|
89
|
+
def body_params(parsed)
|
|
90
|
+
parsed.is_a?(::Hash) ? parsed : {FALLBACK_KEY => parsed}
|
|
105
91
|
end
|
|
106
92
|
end
|
|
107
93
|
end
|
|
@@ -7,13 +7,30 @@ module Hanami
|
|
|
7
7
|
#
|
|
8
8
|
# @since 0.3.0
|
|
9
9
|
# @api private
|
|
10
|
-
VALUE_DIRECTIVES
|
|
10
|
+
VALUE_DIRECTIVES = %i[
|
|
11
|
+
max_age
|
|
12
|
+
max_stale
|
|
13
|
+
min_fresh
|
|
14
|
+
s_maxage
|
|
15
|
+
stale_if_error
|
|
16
|
+
stale_while_revalidate
|
|
17
|
+
].freeze
|
|
11
18
|
|
|
12
19
|
# Cache-Control directives which are implicitly true
|
|
13
20
|
#
|
|
14
21
|
# @since 0.3.0
|
|
15
22
|
# @api private
|
|
16
|
-
NON_VALUE_DIRECTIVES
|
|
23
|
+
NON_VALUE_DIRECTIVES = %i[
|
|
24
|
+
immutable
|
|
25
|
+
must_revalidate
|
|
26
|
+
must_understand
|
|
27
|
+
no_cache
|
|
28
|
+
no_store
|
|
29
|
+
no_transform
|
|
30
|
+
private
|
|
31
|
+
proxy_revalidate
|
|
32
|
+
public
|
|
33
|
+
].freeze
|
|
17
34
|
|
|
18
35
|
# Class representing value directives
|
|
19
36
|
#
|
|
@@ -41,7 +58,7 @@ module Hanami
|
|
|
41
58
|
# @since 0.3.0
|
|
42
59
|
# @api private
|
|
43
60
|
def valid?
|
|
44
|
-
VALUE_DIRECTIVES.include?
|
|
61
|
+
VALUE_DIRECTIVES.include?(@name)
|
|
45
62
|
end
|
|
46
63
|
end
|
|
47
64
|
|
|
@@ -71,7 +88,7 @@ module Hanami
|
|
|
71
88
|
# @since 0.3.0
|
|
72
89
|
# @api private
|
|
73
90
|
def valid?
|
|
74
|
-
NON_VALUE_DIRECTIVES.include?
|
|
91
|
+
NON_VALUE_DIRECTIVES.include?(@name)
|
|
75
92
|
end
|
|
76
93
|
end
|
|
77
94
|
|
|
@@ -104,7 +121,11 @@ module Hanami
|
|
|
104
121
|
# @since 0.3.0
|
|
105
122
|
# @api private
|
|
106
123
|
def <<(directive)
|
|
107
|
-
|
|
124
|
+
unless directive.valid?
|
|
125
|
+
raise ArgumentError.new("Unknown cache control directive: #{directive.name.inspect}")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
@directives << directive
|
|
108
129
|
end
|
|
109
130
|
|
|
110
131
|
# @since 0.3.0
|
|
@@ -235,34 +235,40 @@ module Hanami
|
|
|
235
235
|
# @api private
|
|
236
236
|
RACK_INPUT = ::Rack::RACK_INPUT
|
|
237
237
|
|
|
238
|
-
# The key that
|
|
238
|
+
# The key that holds the body parser's verbatim output for the request.
|
|
239
239
|
#
|
|
240
|
-
# This
|
|
240
|
+
# This holds whatever the parser returned, and may be any type (a hash for most bodies, but
|
|
241
|
+
# possibly an array or scalar for JSON bodies). It mirrors the router's {ROUTER_PARSED_BODY} as
|
|
242
|
+
# the faithful representation of the parsed body.
|
|
243
|
+
#
|
|
244
|
+
# For the params-ready form (always a hash, with keys merged into the request params), see
|
|
245
|
+
# {ACTION_BODY_PARAMS}.
|
|
241
246
|
#
|
|
242
|
-
# @since x.x.x
|
|
243
247
|
# @api private
|
|
244
248
|
ACTION_PARSED_BODY = "hanami.action.parsed_body"
|
|
245
249
|
|
|
246
|
-
# The key that
|
|
250
|
+
# The key that holds the parsed body normalized into a hash for merging into the request params.
|
|
251
|
+
#
|
|
252
|
+
# Hash bodies keep the string keys sent by the client; these are symbolized later by
|
|
253
|
+
# {Hanami::Action::Params}.. Non-Hash bodies (such as a top-level JSON array) are wrapped under
|
|
254
|
+
# `"_"` so they can still be merged.
|
|
247
255
|
#
|
|
248
|
-
#
|
|
256
|
+
# For the parser's verbatim (possibly non-hash) output, see {ACTION_PARSED_BODY}.
|
|
249
257
|
#
|
|
250
|
-
# @since x.x.x
|
|
251
258
|
# @api private
|
|
252
259
|
ACTION_BODY_PARAMS = "hanami.action.body_params"
|
|
253
260
|
|
|
254
261
|
# The key that returns a request body parsed by Hanami Router.
|
|
255
262
|
#
|
|
256
|
-
# This is maintained for
|
|
263
|
+
# This is maintained for compatibility with Hanami Router's body parser middlware.
|
|
257
264
|
#
|
|
258
265
|
# @api private
|
|
259
266
|
ROUTER_PARSED_BODY = "router.parsed_body"
|
|
260
267
|
|
|
261
268
|
# The key that returns router params from the Rack env.
|
|
262
269
|
#
|
|
263
|
-
# This is maintained for
|
|
270
|
+
# This is maintained for compatibility with Hanami Router's body parser middlware.
|
|
264
271
|
#
|
|
265
|
-
# @since 2.0.0
|
|
266
272
|
# @api private
|
|
267
273
|
ROUTER_PARAMS = "router.params"
|
|
268
274
|
|
data/lib/hanami/action/errors.rb
CHANGED
|
@@ -332,12 +332,17 @@ module Hanami
|
|
|
332
332
|
|
|
333
333
|
# Specifies the response freshness policy for HTTP caches using the `Cache-Control` header.
|
|
334
334
|
#
|
|
335
|
-
#
|
|
336
|
-
# `:must_revalidate`, `:proxy_revalidate`) may be passed along with a Hash of value directives
|
|
337
|
-
# (`:max_age`, `:min_stale`, `:s_max_age`).
|
|
335
|
+
# Directives come in two forms:
|
|
338
336
|
#
|
|
339
|
-
#
|
|
340
|
-
#
|
|
337
|
+
# - Non-value directives (such as `:public` or `:no_store`) are passed as bare symbols.
|
|
338
|
+
# - Value directives (such as `max_age:` or `s_maxage:`) are passed as a trailing Hash.
|
|
339
|
+
#
|
|
340
|
+
# See the `@option` entries below for the full set of supported directives. Passing an unknown
|
|
341
|
+
# directive raises an `ArgumentError`.
|
|
342
|
+
#
|
|
343
|
+
# See [MDN's Cache-Control reference][mdn] for more on the standard cache control directives.
|
|
344
|
+
#
|
|
345
|
+
# [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control
|
|
341
346
|
#
|
|
342
347
|
# @example
|
|
343
348
|
# # Set Cache-Control directives
|
|
@@ -349,15 +354,21 @@ module Hanami
|
|
|
349
354
|
# response.get_header("Cache-Control") # => "private, no-store, max-age=900"
|
|
350
355
|
#
|
|
351
356
|
# @param values [Array<Symbol, Hash>] values to map to `Cache-Control` directives
|
|
352
|
-
# @option values [Symbol] :
|
|
353
|
-
# @option values [Symbol] :
|
|
357
|
+
# @option values [Symbol] :immutable
|
|
358
|
+
# @option values [Symbol] :must_revalidate
|
|
359
|
+
# @option values [Symbol] :must_understand
|
|
354
360
|
# @option values [Symbol] :no_cache
|
|
355
361
|
# @option values [Symbol] :no_store
|
|
356
|
-
# @option values [Symbol] :
|
|
362
|
+
# @option values [Symbol] :no_transform
|
|
363
|
+
# @option values [Symbol] :private
|
|
357
364
|
# @option values [Symbol] :proxy_revalidate
|
|
365
|
+
# @option values [Symbol] :public
|
|
358
366
|
# @option values [Hash] :max_age
|
|
359
|
-
# @option values [Hash] :
|
|
360
|
-
# @option values [Hash] :
|
|
367
|
+
# @option values [Hash] :max_stale
|
|
368
|
+
# @option values [Hash] :min_fresh
|
|
369
|
+
# @option values [Hash] :s_maxage
|
|
370
|
+
# @option values [Hash] :stale_if_error
|
|
371
|
+
# @option values [Hash] :stale_while_revalidate
|
|
361
372
|
#
|
|
362
373
|
# @return void
|
|
363
374
|
#
|
data/lib/hanami/action.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hanami-action
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hanakai team
|
|
@@ -147,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
147
147
|
- !ruby/object:Gem::Version
|
|
148
148
|
version: '0'
|
|
149
149
|
requirements: []
|
|
150
|
-
rubygems_version:
|
|
150
|
+
rubygems_version: 3.6.9
|
|
151
151
|
specification_version: 4
|
|
152
152
|
summary: Complete, fast and testable actions for Rack and Hanami
|
|
153
153
|
test_files: []
|