hanami 2.0.0.beta3 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/hanami.gemspec +9 -8
  4. data/lib/hanami/app.rb +50 -39
  5. data/lib/hanami/assets/app_config.rb +61 -0
  6. data/lib/hanami/assets/{configuration.rb → config.rb} +9 -10
  7. data/lib/hanami/{configuration → config}/actions/content_security_policy.rb +3 -3
  8. data/lib/hanami/config/actions/cookies.rb +57 -0
  9. data/lib/hanami/config/actions/sessions.rb +83 -0
  10. data/lib/hanami/config/actions.rb +164 -0
  11. data/lib/hanami/config/logger.rb +176 -0
  12. data/lib/hanami/config/null_config.rb +14 -0
  13. data/lib/hanami/{configuration → config}/router.rb +8 -9
  14. data/lib/hanami/{configuration → config}/views.rb +16 -20
  15. data/lib/hanami/config.rb +396 -0
  16. data/lib/hanami/constants.rb +4 -0
  17. data/lib/hanami/errors.rb +20 -0
  18. data/lib/hanami/extensions/action/slice_configured_action.rb +10 -6
  19. data/lib/hanami/extensions/action.rb +59 -7
  20. data/lib/hanami/extensions/view/context.rb +3 -4
  21. data/lib/hanami/extensions/view/slice_configured_view.rb +4 -4
  22. data/lib/hanami/extensions/view.rb +7 -5
  23. data/lib/hanami/providers/inflector.rb +6 -2
  24. data/lib/hanami/providers/logger.rb +9 -3
  25. data/lib/hanami/providers/rack.rb +12 -2
  26. data/lib/hanami/providers/routes.rb +14 -6
  27. data/lib/hanami/routes.rb +36 -1
  28. data/lib/hanami/settings/env_store.rb +4 -4
  29. data/lib/hanami/settings.rb +169 -21
  30. data/lib/hanami/slice/router.rb +38 -16
  31. data/lib/hanami/slice/routing/middleware/stack.rb +108 -40
  32. data/lib/hanami/slice/routing/resolver.rb +10 -17
  33. data/lib/hanami/slice/view_name_inferrer.rb +1 -1
  34. data/lib/hanami/slice.rb +605 -51
  35. data/lib/hanami/slice_configurable.rb +1 -1
  36. data/lib/hanami/slice_registrar.rb +25 -14
  37. data/lib/hanami/version.rb +2 -3
  38. data/lib/hanami/web/rack_logger.rb +14 -4
  39. data/lib/hanami.rb +122 -24
  40. data/spec/integration/action/csrf_protection_spec.rb +1 -1
  41. data/spec/integration/container/application_routes_helper_spec.rb +3 -1
  42. data/spec/integration/container/prepare_container_spec.rb +2 -0
  43. data/spec/integration/container/provider_lifecycle_spec.rb +61 -0
  44. data/spec/integration/container/standard_providers/rack_provider_spec.rb +44 -0
  45. data/spec/integration/container/{standard_bootable_components_spec.rb → standard_providers_spec.rb} +3 -3
  46. data/spec/integration/rack_app/body_parser_spec.rb +111 -0
  47. data/spec/integration/rack_app/middleware_spec.rb +455 -3
  48. data/spec/integration/rack_app/non_booted_rack_app_spec.rb +2 -1
  49. data/spec/integration/rack_app/rack_app_spec.rb +39 -11
  50. data/spec/integration/settings/access_in_slice_class_body_spec.rb +82 -0
  51. data/spec/integration/settings/access_to_constants_spec.rb +23 -146
  52. data/spec/integration/{slices/slice_settings_spec.rb → settings/slice_registration_spec.rb} +5 -1
  53. data/spec/integration/settings/using_types_spec.rb +4 -11
  54. data/spec/integration/setup_spec.rb +4 -4
  55. data/spec/integration/slices/external_slice_spec.rb +2 -1
  56. data/spec/integration/slices/slice_configuration_spec.rb +3 -1
  57. data/spec/integration/slices/slice_loading_spec.rb +4 -4
  58. data/spec/integration/slices/slice_routing_spec.rb +4 -3
  59. data/spec/integration/slices_spec.rb +100 -0
  60. data/spec/isolation/hanami/boot/success_spec.rb +1 -1
  61. data/spec/support/app_integration.rb +10 -15
  62. data/spec/unit/hanami/{configuration → config}/actions/content_security_policy_spec.rb +16 -16
  63. data/spec/unit/hanami/{configuration → config}/actions/cookies_spec.rb +6 -6
  64. data/spec/unit/hanami/{configuration → config}/actions/csrf_protection_spec.rb +12 -12
  65. data/spec/unit/hanami/config/actions/default_values_spec.rb +54 -0
  66. data/spec/unit/hanami/{configuration → config}/actions/sessions_spec.rb +6 -8
  67. data/spec/unit/hanami/{configuration → config}/actions_spec.rb +8 -20
  68. data/spec/unit/hanami/{configuration → config}/base_url_spec.rb +2 -2
  69. data/spec/unit/hanami/{configuration → config}/inflector_spec.rb +2 -2
  70. data/spec/unit/hanami/{configuration → config}/logger_spec.rb +42 -59
  71. data/spec/unit/hanami/{configuration → config}/router_spec.rb +7 -8
  72. data/spec/unit/hanami/{configuration → config}/slices_spec.rb +2 -2
  73. data/spec/unit/hanami/{configuration → config}/views_spec.rb +13 -24
  74. data/spec/unit/hanami/settings_spec.rb +65 -10
  75. data/spec/unit/hanami/slice_configurable_spec.rb +21 -2
  76. data/spec/unit/hanami/slice_spec.rb +32 -0
  77. data/spec/unit/hanami/version_spec.rb +1 -1
  78. data/spec/unit/hanami/web/rack_logger_spec.rb +13 -2
  79. metadata +100 -76
  80. data/lib/hanami/assets/app_configuration.rb +0 -69
  81. data/lib/hanami/configuration/actions/cookies.rb +0 -29
  82. data/lib/hanami/configuration/actions/sessions.rb +0 -46
  83. data/lib/hanami/configuration/actions.rb +0 -101
  84. data/lib/hanami/configuration/logger.rb +0 -87
  85. data/lib/hanami/configuration/null_configuration.rb +0 -14
  86. data/lib/hanami/configuration/sessions.rb +0 -50
  87. data/lib/hanami/configuration.rb +0 -234
  88. data/lib/hanami/providers/settings.rb +0 -98
  89. data/spec/unit/hanami/configuration/actions/default_values_spec.rb +0 -52
  90. data/spec/unit/hanami/configuration_spec.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52462a1be45d338394f82872ac25bd8fc6e34f5b90991e74928342d0905d377d
4
- data.tar.gz: 221af8ef1ba7993d6eb0678c0536513fca19f68b83bcf13d2fa5664747b83b98
3
+ metadata.gz: 6a1a430f511f0bda44248e88d4fb2c87d10dc3d8fbabf092b895ff2a3bbb78db
4
+ data.tar.gz: 75c6e767584d34103505eb3637fc0fb72dde6118b35fa5f4a76a186681031e7a
5
5
  SHA512:
6
- metadata.gz: f2e0f16b7bee12601ebe28645be73219dd97647d4dc2ddfa47af25ac974092afa8d4b4ec849e0f7866e266c6dd7f39ddf31aa19b857083fb1841f19b98affdbf
7
- data.tar.gz: 1b2ad0280e15291bba6fdf5efaf75bb10555eba655087ea53a9c5c7e8a5522335e0e07e0f2bf81f2b2c4ce638c14a58bbe2d8c827a792a5d2fc17111245bb75a
6
+ metadata.gz: 306e3261fb72d114ddcb9128de3a3c9a499bff57f1b2c51fd3cee54e644248c3bd981f54b626d7e5549e1fa9c467d86156a7c2c3e4c14c869501e698d07f6423
7
+ data.tar.gz: a59bf43bf07700e282abe6e83919fd53a5e277b7efa5706b743a25537006eef76b49409bedf0af49cae8122bd39d5c396f705f1e148a01367565e1accb46d9a6
data/CHANGELOG.md CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
3
  The web, with simplicity.
4
4
 
5
+ ## v2.0.0.rc1 - 2022-11-08
6
+
7
+ ### Added
8
+
9
+ - [Piotr Solnica] Use Zeitwerk to auto-load Hanami
10
+ - [Tim Riley] Introduce `Hanami::Slice.stop` to properly shutdown all the application slices
11
+
12
+ ### Fixed
13
+
14
+ - [Luca Guidi] Ensure to properly mount Rack middleware in routing scope and slice
15
+ - [Tim Riley] Simplify and clarify usage of `Hanami::Config#enviroment`
16
+ - [Tim Riley] Improve error message for missing action class
17
+ - [Tim Riley] Expect nested slices to use parent’s namespace
18
+
19
+ ### Changed
20
+
21
+ - [Piotr Solnica] Replace `Hanami::Logger` with `Dry::Logger`
22
+ - [Tim Riley] Remove duplicated configuration `config.session` and keep `config.actions.sessions`
23
+
24
+ ## v2.0.0.beta4 - 2022-10-24
25
+
26
+ ### Added
27
+
28
+ - [Peter Solnica] Improve middleware config by avoiding class names and requires: symbols instead of classes can be passed as middleware identifiers (`config.middleware.use(:body_parser, :json)`); constant namespaces can be configured for resolving symbols into class names (`config.middleware.namespaces = [Hanami::Middleware, MyStuff::Middlewares]`), with the first match winning; first-party middleware classes are required automatically. (#1215)
29
+
30
+ ### Fixed
31
+
32
+ - [Luca Guidi] Do not attempt to load routes if hanami-router is not bundled (#1214)
33
+ - [Marc Busqué] Ensure nested slices are considered when auto-configuring application components using `Hanami::SliceConfigurable` (#1212)
34
+ - [Tim Riley] Ensure errors are raised during settings loading for any settings that are missing entirely from ENV (#1222)
35
+
36
+ ### Changed
37
+
38
+ - [Tim Riley] Rename `Hanami::App.configuration` to `Hanami::App.config` and `Hanami::Configuration` to `Hanami::Config` (#1218)
39
+ - [Tim Riley] Make `Hanami::App.settings` available as a class method again (#1213)
40
+ - [Tim Riley] `config/settings.rb` in apps no longer has access to autoloaded constants (changed back to behavior pre-beta3) (#1213)
41
+ - [Tim Riley] Auto-generate a nested `Types` module in `Hanami::Settings` subclasses if dry-types is available, for added convenience in type checking settings (#1213)
42
+ - [Marc Busqué] Hide values from `Hanami::Settings#inspect` (values may contain sensitive data). Values can be inspected via `#inspect_values` (#1217)
43
+
5
44
  ## v2.0.0.beta3 - 2022-09-21
6
45
 
7
46
  ### Changed
data/hanami.gemspec CHANGED
@@ -32,14 +32,15 @@ Gem::Specification.new do |spec|
32
32
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
33
33
 
34
34
  spec.add_dependency "bundler", ">= 1.16", "< 3"
35
- spec.add_dependency "dry-configurable", "~> 0.15"
36
- spec.add_dependency "dry-core", "~> 0.7"
37
- spec.add_dependency "dry-inflector", "~> 0.2", ">= 0.2.1"
38
- spec.add_dependency "dry-system", "~> 0.25", ">= 0.25.0"
39
- spec.add_dependency "dry-monitor", "~> 0.6", ">= 0.6.3"
40
- spec.add_dependency "hanami-cli", "~> 2.0.beta"
41
- spec.add_dependency "hanami-utils", "~> 2.0.beta"
42
- spec.add_dependency "zeitwerk", "~> 2.4"
35
+ spec.add_dependency "dry-configurable", "~> 1.0", "< 2"
36
+ spec.add_dependency "dry-core", "~> 1.0", "< 2"
37
+ spec.add_dependency "dry-inflector", "~> 1.0", "< 2"
38
+ spec.add_dependency "dry-monitor", "~> 1.0", "< 2"
39
+ spec.add_dependency "dry-system", "~> 1.0.rc"
40
+ spec.add_dependency "dry-logger", "~> 1.0.rc"
41
+ spec.add_dependency "hanami-cli", "~> 2.0.rc"
42
+ spec.add_dependency "hanami-utils", "~> 2.0.rc"
43
+ spec.add_dependency "zeitwerk", "~> 2.6"
43
44
 
44
45
  spec.add_development_dependency "rspec", "~> 3.8"
45
46
  spec.add_development_dependency "rack-test", "~> 1.1"
data/lib/hanami/app.rb CHANGED
@@ -1,17 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "configuration"
4
3
  require_relative "constants"
5
- require_relative "slice"
6
- require_relative "slice_name"
7
4
 
8
5
  module Hanami
9
- # The Hanami app is a singular slice tasked with managing the core components of
10
- # the app and coordinating overall app boot.
6
+ # The Hanami app is a singular slice tasked with managing the core components of the app and
7
+ # coordinating overall app boot.
11
8
  #
12
- # For smaller apps, the app may be the only slice present, whereas larger apps
13
- # may consist of many slices, with the app reserved for holding a small number
14
- # of shared components only.
9
+ # For smaller apps, the app may be the only slice present, whereas larger apps may consist of many
10
+ # slices, with the app reserved for holding a small number of shared components only.
15
11
  #
16
12
  # @see Slice
17
13
  #
@@ -20,6 +16,8 @@ module Hanami
20
16
  class App < Slice
21
17
  @_mutex = Mutex.new
22
18
 
19
+ # @api private
20
+ # @since 2.0.0
23
21
  def self.inherited(subclass)
24
22
  super
25
23
 
@@ -29,11 +27,11 @@ module Hanami
29
27
 
30
28
  @_mutex.synchronize do
31
29
  subclass.class_eval do
32
- @configuration = Hanami::Configuration.new(app_name: slice_name, env: Hanami.env)
30
+ @config = Hanami::Config.new(app_name: slice_name, env: Hanami.env)
33
31
 
34
- # Prepare the load path (based on the default root of `Dir.pwd`) as early as
35
- # possible, so you can make a `require` inside the body of an `App` subclass,
36
- # which may be useful for certain kinds of app configuration.
32
+ # Prepare the load path (based on the default root of `Dir.pwd`) as early as possible, so
33
+ # you can make a `require` inside the body of an `App` subclass, which may be useful for
34
+ # certain kinds of app configuration.
37
35
  prepare_load_path
38
36
 
39
37
  load_dotenv
@@ -43,29 +41,41 @@ module Hanami
43
41
 
44
42
  # App class interface
45
43
  module ClassMethods
46
- attr_reader :configuration
44
+ # Returns the app's config.
45
+ #
46
+ # @return [Hanami::Config]
47
+ #
48
+ # @api public
49
+ # @since 2.0.0
50
+ attr_reader :config
47
51
 
52
+ # Returns the app's {SliceName}.
53
+ #
54
+ # @return [Hanami::SliceName]
55
+ #
56
+ # @see Slice::ClassMethods#slice_name
57
+ #
58
+ # @api public
59
+ # @since 2.0.0
48
60
  def app_name
49
61
  slice_name
50
62
  end
51
63
 
52
- # Prepares the $LOAD_PATH based on the app's configured root, prepending the `lib/`
53
- # directory if it exists. If the lib directory is already added, this will do
54
- # nothing.
64
+ # Prepares the $LOAD_PATH based on the app's configured root, prepending the `lib/` directory
65
+ # if it exists. If the lib directory is already added, this will do nothing.
55
66
  #
56
- # In ordinary circumstances, you should never have to call this method: this method
57
- # is called immediately upon subclassing {Hanami::App}, as a convenicence to put
58
- # lib/ (under the default root of `Dir.pwd`) on the load path automatically. This is
59
- # helpful if you need to require files inside the subclass body for performing
60
- # certain app configuration steps.
67
+ # In ordinary circumstances, you should never have to call this method: this method is called
68
+ # immediately upon subclassing {Hanami::App}, as a convenicence to put lib/ (under the default
69
+ # root of `Dir.pwd`) on the load path automatically. This is helpful if you need to require
70
+ # files inside the subclass body for performing certain app configuration steps.
61
71
  #
62
- # If you change your app's `config.root` and you need to require files from its
63
- # `lib/` directory within your {App} subclass body, you should call
64
- # {.prepare_load_path} explicitly after setting the new root.
72
+ # If you change your app's `config.root` and you need to require files from its `lib/`
73
+ # directory within your {App} subclass body, you should call {.prepare_load_path} explicitly
74
+ # after setting the new root.
65
75
  #
66
- # Otherwise, this method is called again as part of the app {.prepare} step, so if
67
- # you've changed your app's root and do _not_ need to require files within your {App}
68
- # subclass body, then you don't need to call this method.
76
+ # Otherwise, this method is called again as part of the app {.prepare} step, so if you've
77
+ # changed your app's root and do _not_ need to require files within your {App} subclass body,
78
+ # then you don't need to call this method.
69
79
  #
70
80
  # @example
71
81
  # module MyApp
@@ -124,12 +134,12 @@ module Hanami
124
134
  # Make app-wide notifications available as early as possible
125
135
  container.use(:notifications)
126
136
 
127
- # Ensure all basic slice preparation is complete before we make adjustments below
128
- # (which rely on the basic prepare steps having already run)
137
+ # Ensure all basic slice preparation is complete before we make adjustments below (which
138
+ # rely on the basic prepare steps having already run)
129
139
  super
130
140
 
131
- # Run specific prepare steps for the app slice. Note also that some
132
- # standard steps have been skipped via the empty method overrides below.
141
+ # Run specific prepare steps for the app slice. Note also that some standard steps have been
142
+ # skipped via the empty method overrides below.
133
143
  prepare_app_component_dirs
134
144
  prepare_app_providers
135
145
  end
@@ -150,9 +160,9 @@ module Hanami
150
160
  end
151
161
  end
152
162
 
153
- # When auto-registering components in app/, ignore files in `app/lib/` (these will
154
- # be auto-registered as above), as well as the configured no_auto_register_paths
155
- no_auto_register_paths = ([LIB_DIR] + configuration.no_auto_register_paths)
163
+ # When auto-registering components in app/, ignore files in `app/lib/` (these will be
164
+ # auto-registered as above), as well as the configured no_auto_register_paths
165
+ no_auto_register_paths = ([LIB_DIR] + config.no_auto_register_paths)
156
166
  .map { |path|
157
167
  path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
158
168
  }
@@ -178,14 +188,15 @@ module Hanami
178
188
  register_provider(:logger, source: Hanami::Providers::Logger)
179
189
  end
180
190
 
181
- require_relative "providers/rack"
182
- register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
191
+ if Hanami.bundled?("rack")
192
+ require_relative "providers/rack"
193
+ register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
194
+ end
183
195
  end
184
196
 
185
197
  def prepare_autoloader
186
- # Component dirs are automatically pushed to the autoloader by dry-system's
187
- # zeitwerk plugin. This method adds other dirs that are not otherwise configured
188
- # as component dirs.
198
+ # Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
199
+ # This method adds other dirs that are not otherwise configured as component dirs.
189
200
 
190
201
  # Autoload classes from `lib/[app_namespace]/`
191
202
  if root.join(LIB_DIR, app_name.name).directory?
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+
5
+ module Hanami
6
+ # @api private
7
+ module Assets
8
+ # App config for assets.
9
+ #
10
+ # This is NOT RELEASED as of 2.0.0.
11
+ #
12
+ # @api private
13
+ class AppConfig
14
+ include Dry::Configurable
15
+
16
+ attr_reader :base_config
17
+ protected :base_config
18
+
19
+ setting :server_url, default: "http://localhost:8080"
20
+
21
+ def initialize(*)
22
+ super
23
+
24
+ @base_config = Assets::Config.new
25
+ end
26
+
27
+ def initialize_copy(source)
28
+ super
29
+ @base_config = source.base_config.dup
30
+ end
31
+
32
+ def finalize!
33
+ end
34
+
35
+ # Returns the list of available settings
36
+ #
37
+ # @return [Set]
38
+ #
39
+ # @api private
40
+ def settings
41
+ base_config.settings + self.class.settings
42
+ end
43
+
44
+ private
45
+
46
+ def method_missing(name, *args, &block)
47
+ if config.respond_to?(name)
48
+ config.public_send(name, *args, &block)
49
+ elsif base_config.respond_to?(name)
50
+ base_config.public_send(name, *args, &block)
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def respond_to_missing?(name, _incude_all = false)
57
+ config.respond_to?(name) || base_config.respond_to?(name) || super
58
+ end
59
+ end
60
+ end
61
+ end
@@ -4,16 +4,19 @@ require "dry/configurable"
4
4
 
5
5
  module Hanami
6
6
  module Assets
7
- # @since 2.0.0
8
- # @api public
9
- class Configuration
7
+ # App config for assets.
8
+ #
9
+ # This is NOT RELEASED as of 2.0.0.
10
+ #
11
+ # @api private
12
+ class Config
10
13
  include Dry::Configurable
11
14
 
12
- # Initialize the Configuration
15
+ # Initialize the Config
13
16
  #
14
- # @yield [config] the configuration object
17
+ # @yield [config] the config object
15
18
  #
16
- # @return [Configuration]
19
+ # @return [Config]
17
20
  #
18
21
  # @since 2.0.0
19
22
  # @api private
@@ -34,8 +37,6 @@ module Hanami
34
37
 
35
38
  private
36
39
 
37
- # @since 2.0.0
38
- # @api private
39
40
  def method_missing(name, *args, &block)
40
41
  if config.respond_to?(name)
41
42
  config.public_send(name, *args, &block)
@@ -44,8 +45,6 @@ module Hanami
44
45
  end
45
46
  end
46
47
 
47
- # @since 2.0.0
48
- # @api private
49
48
  def respond_to_missing?(name, _incude_all = false)
50
49
  config.respond_to?(name) || super
51
50
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hanami
4
- class Configuration
4
+ class Config
5
5
  class Actions
6
- # Configuration for Content Security Policy in Hanami apps
6
+ # Config for Content Security Policy in Hanami apps
7
7
  #
8
8
  # @since 2.0.0
9
9
  class ContentSecurityPolicy
@@ -99,7 +99,7 @@ module Hanami
99
99
 
100
100
  # @since 2.0.0
101
101
  # @api private
102
- def to_str
102
+ def to_s
103
103
  @policy.map do |key, value|
104
104
  "#{dasherize(key)} #{value}"
105
105
  end.join(";\n")
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Config
5
+ class Actions
6
+ # Wrapper for app-level config of HTTP cookies for Hanami actions.
7
+ #
8
+ # This decorates the hash of cookie options that is otherwise directly configurable on
9
+ # actions, and adds the `enabled?` method to allow app base action to determine whether to
10
+ # include the `Action::Cookies` module.
11
+ #
12
+ # @api public
13
+ # @since 2.0.0
14
+ class Cookies
15
+ # Returns the cookie options.
16
+ #
17
+ # @return [Hash]
18
+ #
19
+ # @api public
20
+ # @since 2.0.0
21
+ attr_reader :options
22
+
23
+ # Returns a new `Cookies`.
24
+ #
25
+ # You should not need to initialize this class directly. Instead use
26
+ # {Hanami::Config::Actions#cookies}.
27
+ #
28
+ # @api private
29
+ # @since 2.0.0
30
+ def initialize(options)
31
+ @options = options
32
+ end
33
+
34
+ # Returns true if any cookie options have been provided.
35
+ #
36
+ # @return [Boolean]
37
+ #
38
+ # @api public
39
+ # @since 2.0.0
40
+ def enabled?
41
+ !options.nil?
42
+ end
43
+
44
+ # Returns the cookie options.
45
+ #
46
+ # If no options have been provided, returns an empty hash.
47
+ #
48
+ # @return [Hash]
49
+ #
50
+ # @api public
51
+ def to_h
52
+ options.to_h
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/string"
4
+ require "hanami/utils/class"
5
+
6
+ module Hanami
7
+ class Config
8
+ class Actions
9
+ # Config for HTTP session middleware in Hanami actions.
10
+ #
11
+ # @api public
12
+ # @since 2.0.0
13
+ class Sessions
14
+ # Returns the configured session storage
15
+ #
16
+ # @return [Symbol]
17
+ #
18
+ # @api public
19
+ # @since 2.0.0
20
+ attr_reader :storage
21
+
22
+ # Returns the configured session storage options
23
+ #
24
+ # @return [Array]
25
+ #
26
+ # @api public
27
+ # @since 2.0.0
28
+ attr_reader :options
29
+
30
+ # Returns a new `Sessions`.
31
+ #
32
+ # You should not need to initialize this class directly. Instead use
33
+ # {Hanami::Config::Actions#sessions=}.
34
+ #
35
+ # @example
36
+ # config.actions.sessions = :cookie, {secret: "xyz"}
37
+ #
38
+ # @api private
39
+ # @since 2.0.0
40
+ def initialize(storage = nil, *options)
41
+ @storage = storage
42
+ @options = options
43
+ end
44
+
45
+ # Returns true if sessions have been enabled.
46
+ #
47
+ # @return [Boolean]
48
+ #
49
+ # @api public
50
+ # @since 2.0.0
51
+ def enabled?
52
+ !storage.nil?
53
+ end
54
+
55
+ # Returns an array of the session storage middleware name and its options, or an empty array
56
+ # if sessions have not been enabled.
57
+ #
58
+ # @return [Array<(Symbol, Array)>]
59
+ #
60
+ # @api public
61
+ # @since 2.0.0
62
+ def middleware
63
+ return [] unless enabled?
64
+
65
+ [storage_middleware, options].flatten(1)
66
+ end
67
+
68
+ private
69
+
70
+ def storage_middleware
71
+ require_storage
72
+
73
+ name = Utils::String.classify(storage)
74
+ Utils::Class.load!(name, ::Rack::Session)
75
+ end
76
+
77
+ def require_storage
78
+ require "rack/session/#{storage}"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+
5
+ module Hanami
6
+ class Config
7
+ # Hanami actions config
8
+ #
9
+ # This exposes all the settings from the standalone `Hanami::Action` class, pre-configured with
10
+ # sensible defaults for actions within a full Hanami app. It also provides additional settings
11
+ # for further integration of actions with other full stack app components.
12
+ #
13
+ # @since 2.0.0
14
+ # @api public
15
+ class Actions
16
+ include Dry::Configurable
17
+
18
+ # @!attribute [rw] cookies
19
+ # Sets or returns a hash of cookie options for actions.
20
+ #
21
+ # The hash is wrapped by {Hanami::Config::Actions::Cookies}, which also provides an
22
+ # `enabled?` method, returning true in the case of any options provided.
23
+ #
24
+ # @example
25
+ # config.actions.cookies = {max_age: 300}
26
+ #
27
+ # @return [Hanami::Config::Actions::Cookies]
28
+ #
29
+ # @api public
30
+ # @since 2.0.0
31
+ setting :cookies, default: {}, constructor: -> options { Cookies.new(options) }
32
+
33
+ # @!attribute [rw] sessions
34
+ # Sets or returns the session store (and its options) for actions.
35
+ #
36
+ # The given values are taken as an argument list to be passed to {Config::Sessions#initialize}.
37
+ #
38
+ # The configured session store is used when setting up the app or slice
39
+ # {Slice::ClassMethods#router router}.
40
+ #
41
+ # @example
42
+ # config.actions.sessions = :cookie, {secret: "xyz"}
43
+ #
44
+ # @return [Config::Sessions]
45
+ #
46
+ # @see Config::Sessions
47
+ # @see Slice::ClassMethods#router
48
+ #
49
+ # @api public
50
+ # @since 2.0.0
51
+ setting :sessions, constructor: proc { |storage, *options| Sessions.new(storage, *options) }
52
+
53
+ # @!attribute [rw] csrf_protection
54
+ # Sets or returns whether CSRF protection should be enabled for action classes.
55
+ #
56
+ # Defaults to true if {#sessions} is enabled. You can override this by explicitly setting a
57
+ # true or false value.
58
+ #
59
+ # When true, this will include `Hanami::Action::CSRFProtection` in all action classes.
60
+ #
61
+ # @return [Boolean]
62
+ #
63
+ # @api public
64
+ # @since 2.0.0
65
+ setting :csrf_protection
66
+
67
+ # Returns the Content Security Policy config for actions.
68
+ #
69
+ # The resulting policy is set as a default `"Content-Security-Policy"` response header.
70
+ #
71
+ # @return [Hanami::Config::Actions::ContentSecurityPolicy]
72
+ #
73
+ # @api public
74
+ # @since 2.0.0
75
+ attr_accessor :content_security_policy
76
+
77
+ # The following settings are for view and assets integration with actions, and are NOT
78
+ # publicly released as of 2.0.0. We'll make full documentation available when these become
79
+ # public in a subsequent release.
80
+
81
+ # @!attribute [rw] name_inference_base
82
+ # @api private
83
+ setting :name_inference_base, default: "actions"
84
+
85
+ # @!attribute [rw] view_context_identifier
86
+ # @api private
87
+ setting :view_context_identifier, default: "views.context"
88
+
89
+ # @!attribute [rw] view_name_inferrer
90
+ # @api private
91
+ setting :view_name_inferrer, default: Slice::ViewNameInferrer
92
+
93
+ # @!attribute [rw] view_name_inference_base
94
+ # @api private
95
+ setting :view_name_inference_base, default: "views"
96
+
97
+ # @api private
98
+ attr_reader :base_config
99
+ protected :base_config
100
+
101
+ # @api private
102
+ def initialize(*, **options)
103
+ super()
104
+
105
+ @base_config = Hanami::Action.config.dup
106
+ @content_security_policy = ContentSecurityPolicy.new do |csp|
107
+ if assets_server_url = options[:assets_server_url]
108
+ csp[:script_src] += " #{assets_server_url}"
109
+ csp[:style_src] += " #{assets_server_url}"
110
+ end
111
+ end
112
+
113
+ configure_defaults
114
+ end
115
+
116
+ # @api private
117
+ def initialize_copy(source)
118
+ super
119
+ @base_config = source.base_config.dup
120
+ @content_security_policy = source.content_security_policy.dup
121
+ end
122
+ private :initialize_copy
123
+
124
+ # @api private
125
+ def finalize!
126
+ # A nil value for `csrf_protection` means it has not been explicitly configured
127
+ # (neither true nor false), so we can default it to whether sessions are enabled
128
+ self.csrf_protection = sessions.enabled? if csrf_protection.nil?
129
+
130
+ if content_security_policy
131
+ default_headers["Content-Security-Policy"] = content_security_policy.to_s
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ # Apply defaults for base config
138
+ def configure_defaults
139
+ self.default_request_format = :html
140
+ self.default_response_format = :html
141
+
142
+ self.default_headers = {
143
+ "X-Frame-Options" => "DENY",
144
+ "X-Content-Type-Options" => "nosniff",
145
+ "X-XSS-Protection" => "1; mode=block"
146
+ }
147
+ end
148
+
149
+ def method_missing(name, *args, &block)
150
+ if config.respond_to?(name)
151
+ config.public_send(name, *args, &block)
152
+ elsif base_config.respond_to?(name)
153
+ base_config.public_send(name, *args, &block)
154
+ else
155
+ super
156
+ end
157
+ end
158
+
159
+ def respond_to_missing?(name, _incude_all = false)
160
+ config.respond_to?(name) || base_config.respond_to?(name) || super
161
+ end
162
+ end
163
+ end
164
+ end