hanami 2.0.0.beta4 → 2.0.0.rc1
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 +19 -0
- data/hanami.gemspec +8 -7
- data/lib/hanami/app.rb +47 -36
- data/lib/hanami/assets/app_config.rb +7 -15
- data/lib/hanami/assets/config.rb +5 -6
- data/lib/hanami/config/actions/content_security_policy.rb +1 -1
- data/lib/hanami/config/actions/cookies.rb +27 -0
- data/lib/hanami/config/actions/sessions.rb +42 -5
- data/lib/hanami/config/actions.rb +81 -17
- data/lib/hanami/config/logger.rb +112 -23
- data/lib/hanami/config/router.rb +0 -1
- data/lib/hanami/config/views.rb +6 -10
- data/lib/hanami/config.rb +235 -73
- data/lib/hanami/constants.rb +4 -0
- data/lib/hanami/errors.rb +17 -0
- data/lib/hanami/extensions/action/slice_configured_action.rb +9 -5
- data/lib/hanami/extensions/action.rb +59 -7
- data/lib/hanami/extensions/view/context.rb +3 -4
- data/lib/hanami/extensions/view/slice_configured_view.rb +4 -4
- data/lib/hanami/extensions/view.rb +7 -5
- data/lib/hanami/providers/inflector.rb +6 -0
- data/lib/hanami/providers/logger.rb +8 -0
- data/lib/hanami/providers/rack.rb +12 -0
- data/lib/hanami/providers/routes.rb +14 -4
- data/lib/hanami/routes.rb +36 -1
- data/lib/hanami/settings/env_store.rb +1 -1
- data/lib/hanami/settings.rb +102 -36
- data/lib/hanami/slice/router.rb +38 -16
- data/lib/hanami/slice/routing/middleware/stack.rb +66 -42
- data/lib/hanami/slice/routing/resolver.rb +10 -17
- data/lib/hanami/slice/view_name_inferrer.rb +1 -1
- data/lib/hanami/slice.rb +553 -14
- data/lib/hanami/slice_registrar.rb +20 -15
- data/lib/hanami/version.rb +2 -3
- data/lib/hanami/web/rack_logger.rb +14 -4
- data/lib/hanami.rb +122 -23
- data/spec/integration/action/csrf_protection_spec.rb +1 -1
- data/spec/integration/container/application_routes_helper_spec.rb +3 -1
- data/spec/integration/container/provider_lifecycle_spec.rb +61 -0
- data/spec/integration/container/standard_providers/rack_provider_spec.rb +44 -0
- data/spec/integration/container/{standard_bootable_components_spec.rb → standard_providers_spec.rb} +3 -3
- data/spec/integration/rack_app/body_parser_spec.rb +3 -0
- data/spec/integration/rack_app/middleware_spec.rb +427 -3
- data/spec/integration/rack_app/non_booted_rack_app_spec.rb +2 -1
- data/spec/integration/rack_app/rack_app_spec.rb +39 -11
- data/spec/integration/setup_spec.rb +4 -4
- data/spec/integration/slices/external_slice_spec.rb +2 -1
- data/spec/integration/slices/slice_configuration_spec.rb +3 -1
- data/spec/integration/slices/slice_loading_spec.rb +4 -4
- data/spec/integration/slices/slice_routing_spec.rb +4 -3
- data/spec/integration/slices_spec.rb +100 -0
- data/spec/isolation/hanami/boot/success_spec.rb +1 -1
- data/spec/support/app_integration.rb +2 -10
- data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +7 -7
- data/spec/unit/hanami/config/actions/default_values_spec.rb +1 -1
- data/spec/unit/hanami/config/actions/sessions_spec.rb +1 -3
- data/spec/unit/hanami/config/actions_spec.rb +1 -12
- data/spec/unit/hanami/config/logger_spec.rb +38 -55
- data/spec/unit/hanami/config/router_spec.rb +1 -1
- data/spec/unit/hanami/config/views_spec.rb +3 -13
- data/spec/unit/hanami/settings_spec.rb +1 -1
- data/spec/unit/hanami/slice_configurable_spec.rb +5 -5
- data/spec/unit/hanami/slice_spec.rb +32 -0
- data/spec/unit/hanami/version_spec.rb +1 -1
- data/spec/unit/hanami/web/rack_logger_spec.rb +13 -2
- metadata +54 -45
- data/lib/hanami/config/sessions.rb +0 -50
- data/spec/unit/hanami/config_spec.rb +0 -43
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "hanami/action"
|
4
|
-
|
5
3
|
module Hanami
|
6
4
|
module Extensions
|
7
5
|
module Action
|
@@ -35,12 +33,14 @@ module Hanami
|
|
35
33
|
resolve_view = method(:resolve_paired_view)
|
36
34
|
resolve_view_context = method(:resolve_view_context)
|
37
35
|
resolve_routes = method(:resolve_routes)
|
36
|
+
resolve_rack_monitor = method(:resolve_rack_monitor)
|
38
37
|
|
39
38
|
define_method(:new) do |**kwargs|
|
40
39
|
super(
|
41
40
|
view: kwargs.fetch(:view) { resolve_view.(self) },
|
42
41
|
view_context: kwargs.fetch(:view_context) { resolve_view_context.() },
|
43
42
|
routes: kwargs.fetch(:routes) { resolve_routes.() },
|
43
|
+
rack_monitor: kwargs.fetch(:rack_monitor) { resolve_rack_monitor.() },
|
44
44
|
**kwargs,
|
45
45
|
)
|
46
46
|
end
|
@@ -86,12 +86,12 @@ module Hanami
|
|
86
86
|
# the `:json` value configured in its immediate superclass.
|
87
87
|
#
|
88
88
|
# This would be surprising behavior, and we want to avoid it.
|
89
|
-
slice_value = slice.config.actions.public_send(
|
90
|
-
parent_value = slice.parent.config.actions.public_send(
|
89
|
+
slice_value = slice.config.actions.public_send(setting.name)
|
90
|
+
parent_value = slice.parent.config.actions.public_send(setting.name) if slice.parent
|
91
91
|
|
92
92
|
next if slice.parent && slice_value == parent_value
|
93
93
|
|
94
|
-
action_class.config.public_send(:"#{setting}=", slice_value)
|
94
|
+
action_class.config.public_send(:"#{setting.name}=", slice_value)
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
@@ -137,6 +137,10 @@ module Hanami
|
|
137
137
|
slice.app["routes"] if slice.app.key?("routes")
|
138
138
|
end
|
139
139
|
|
140
|
+
def resolve_rack_monitor
|
141
|
+
slice.app["rack.monitor"] if slice.app.key?("rack.monitor")
|
142
|
+
end
|
143
|
+
|
140
144
|
def actions_config
|
141
145
|
slice.config.actions
|
142
146
|
end
|
@@ -1,18 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "hanami/action"
|
4
|
-
require_relative "../slice_configurable"
|
5
|
-
require_relative "action/slice_configured_action"
|
6
4
|
|
7
5
|
module Hanami
|
6
|
+
# @api private
|
8
7
|
module Extensions
|
9
|
-
#
|
8
|
+
# Integrated behavior for `Hanami::Action` classes within Hanami apps.
|
10
9
|
#
|
11
|
-
# @see
|
10
|
+
# @see InstanceMethods
|
11
|
+
# @see https://github.com/hanami/controller
|
12
12
|
#
|
13
13
|
# @api public
|
14
14
|
# @since 2.0.0
|
15
15
|
module Action
|
16
|
+
# @api private
|
16
17
|
def self.included(action_class)
|
17
18
|
super
|
18
19
|
|
@@ -21,46 +22,97 @@ module Hanami
|
|
21
22
|
action_class.prepend(InstanceMethods)
|
22
23
|
end
|
23
24
|
|
25
|
+
# Class methods for app-integrated actions.
|
26
|
+
#
|
27
|
+
# @since 2.0.0
|
24
28
|
module ClassMethods
|
29
|
+
# @api private
|
25
30
|
def configure_for_slice(slice)
|
26
31
|
extend SliceConfiguredAction.new(slice)
|
27
32
|
end
|
28
33
|
end
|
29
34
|
|
35
|
+
# Instance methods for app-integrated actions.
|
36
|
+
#
|
37
|
+
# @since 2.0.0
|
30
38
|
module InstanceMethods
|
31
|
-
|
39
|
+
# @api private
|
40
|
+
attr_reader :view
|
32
41
|
|
33
|
-
|
42
|
+
# @api private
|
43
|
+
attr_reader :view_context
|
44
|
+
|
45
|
+
# Returns the app or slice's {Hanami::Slice::RoutesHelper RoutesHelper} for use within
|
46
|
+
# action instance methods.
|
47
|
+
#
|
48
|
+
# @return [Hanami::Slice::RoutesHelper]
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
# @since 2.0.0
|
52
|
+
attr_reader :routes
|
53
|
+
|
54
|
+
# Returns the app or slice's `Dry::Monitor::Rack::Middleware` for use within
|
55
|
+
# action instance methods.
|
56
|
+
#
|
57
|
+
# @return [Dry::Monitor::Rack::Middleware]
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
# @since 2.0.0
|
61
|
+
attr_reader :rack_monitor
|
62
|
+
|
63
|
+
# @overload def initialize(routes: nil, **kwargs)
|
64
|
+
# Returns a new `Hanami::Action` with app components injected as dependencies.
|
65
|
+
#
|
66
|
+
# These dependencies are injected automatically so that a call to `.new` (with no
|
67
|
+
# arguments) returns a fully integrated action.
|
68
|
+
#
|
69
|
+
# @param routes [Hanami::Slice::RoutesHelper]
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
# @since 2.0.0
|
73
|
+
def initialize(view: nil, view_context: nil, rack_monitor: nil, routes: nil, **kwargs)
|
34
74
|
@view = view
|
35
75
|
@view_context = view_context
|
36
76
|
@routes = routes
|
77
|
+
@rack_monitor = rack_monitor
|
37
78
|
|
38
79
|
super(**kwargs)
|
39
80
|
end
|
40
81
|
|
41
82
|
private
|
42
83
|
|
84
|
+
# @api private
|
43
85
|
def build_response(**options)
|
44
86
|
options = options.merge(view_options: method(:view_options))
|
45
87
|
super(**options)
|
46
88
|
end
|
47
89
|
|
90
|
+
# @api private
|
48
91
|
def finish(req, res, halted)
|
49
92
|
res.render(view, **req.params) if !halted && auto_render?(res)
|
50
93
|
super
|
51
94
|
end
|
52
95
|
|
96
|
+
# @api private
|
97
|
+
def _handle_exception(request, _response, exception)
|
98
|
+
rack_monitor&.instrument(:error, exception: exception, env: request.env)
|
99
|
+
|
100
|
+
super
|
101
|
+
end
|
102
|
+
|
103
|
+
# @api private
|
53
104
|
def view_options(req, res)
|
54
105
|
{context: view_context&.with(**view_context_options(req, res))}.compact
|
55
106
|
end
|
56
107
|
|
108
|
+
# @api private
|
57
109
|
def view_context_options(req, res)
|
58
110
|
{request: req, response: res}
|
59
111
|
end
|
60
112
|
|
61
113
|
# Returns true if a view should automatically be rendered onto the response body.
|
62
114
|
#
|
63
|
-
# This may be overridden to enable
|
115
|
+
# This may be overridden to enable or disable automatic rendering.
|
64
116
|
#
|
65
117
|
# @param res [Hanami::Action::Response]
|
66
118
|
#
|
@@ -3,16 +3,15 @@
|
|
3
3
|
require "hanami/view"
|
4
4
|
require "hanami/view/context"
|
5
5
|
require_relative "../../errors"
|
6
|
-
require_relative "../../slice_configurable"
|
7
|
-
require_relative "slice_configured_context"
|
8
6
|
|
9
7
|
module Hanami
|
10
8
|
module Extensions
|
11
9
|
module View
|
12
10
|
# View context for views in Hanami apps.
|
13
11
|
#
|
14
|
-
#
|
15
|
-
#
|
12
|
+
# This is NOT RELEASED as of 2.0.0.
|
13
|
+
#
|
14
|
+
# @api private
|
16
15
|
module Context
|
17
16
|
def self.included(context_class)
|
18
17
|
super
|
@@ -32,7 +32,7 @@ module Hanami
|
|
32
32
|
# rubocop:disable Metrics/AbcSize
|
33
33
|
def configure_view(view_class)
|
34
34
|
view_class.settings.each do |setting|
|
35
|
-
next unless slice.config.views.respond_to?(
|
35
|
+
next unless slice.config.views.respond_to?(setting.name)
|
36
36
|
|
37
37
|
# Configure the view from config on the slice, _unless it has already been configured by
|
38
38
|
# a parent slice_, and re-configuring it for this slice would make no change.
|
@@ -72,12 +72,12 @@ module Hanami
|
|
72
72
|
# in its immediate superclass.
|
73
73
|
#
|
74
74
|
# This would be surprising behavior, and we want to avoid it.
|
75
|
-
slice_value = slice.config.views.public_send(
|
76
|
-
parent_value = slice.parent.config.views.public_send(
|
75
|
+
slice_value = slice.config.views.public_send(setting.name)
|
76
|
+
parent_value = slice.parent.config.views.public_send(setting.name) if slice.parent
|
77
77
|
|
78
78
|
next if slice.parent && slice_value == parent_value
|
79
79
|
|
80
|
-
view_class.config.public_send(:"#{setting}=", slice_value)
|
80
|
+
view_class.config.public_send(:"#{setting.name}=", slice_value)
|
81
81
|
end
|
82
82
|
|
83
83
|
view_class.config.inflector = inflector
|
@@ -1,18 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "hanami/view"
|
4
|
-
require_relative "../slice_configurable"
|
5
|
-
require_relative "view/slice_configured_view"
|
6
4
|
|
7
5
|
module Hanami
|
6
|
+
# @api private
|
8
7
|
module Extensions
|
9
|
-
#
|
8
|
+
# Integrated behavior for `Hanami::View` classes within Hanami apps.
|
9
|
+
#
|
10
|
+
# This is NOT RELEASED as of 2.0.0.
|
10
11
|
#
|
11
12
|
# @see Hanami::View
|
12
13
|
#
|
13
|
-
# @api
|
14
|
-
# @since 2.0.0
|
14
|
+
# @api private
|
15
15
|
module View
|
16
|
+
# @api private
|
16
17
|
def self.included(view_class)
|
17
18
|
super
|
18
19
|
|
@@ -20,6 +21,7 @@ module Hanami
|
|
20
21
|
view_class.extend(ClassMethods)
|
21
22
|
end
|
22
23
|
|
24
|
+
# @api private
|
23
25
|
module ClassMethods
|
24
26
|
# @api private
|
25
27
|
def configure_for_slice(slice)
|
@@ -1,8 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hanami
|
4
|
+
# @api private
|
4
5
|
module Providers
|
6
|
+
# Provider source to register inflector component in Hanami slices.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 2.0.0
|
5
10
|
class Inflector < Dry::System::Provider::Source
|
11
|
+
# @api private
|
6
12
|
def start
|
7
13
|
register :inflector, Hanami.app.inflector
|
8
14
|
end
|
@@ -1,8 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hanami
|
4
|
+
# @api private
|
4
5
|
module Providers
|
6
|
+
# Provider source to register logger component in Hanami slices.
|
7
|
+
#
|
8
|
+
# @see Hanami::Config#logger
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 2.0.0
|
5
12
|
class Logger < Dry::System::Provider::Source
|
13
|
+
# @api private
|
6
14
|
def start
|
7
15
|
register :logger, Hanami.app.config.logger_instance
|
8
16
|
end
|
@@ -1,8 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hanami
|
4
|
+
# @api private
|
4
5
|
module Providers
|
6
|
+
# Provider source to register Rack integration components in Hanami slices.
|
7
|
+
#
|
8
|
+
# @see Hanami::Providers::Logger
|
9
|
+
# @see Hanami::Web::RackLogger
|
10
|
+
# @see https://github.com/rack/rack
|
11
|
+
# @see https://dry-rb.org/gems/dry-monitor/
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
# @since 2.0.0
|
5
15
|
class Rack < Dry::System::Provider::Source
|
16
|
+
# @api private
|
6
17
|
def prepare
|
7
18
|
require "dry/monitor"
|
8
19
|
require "hanami/web/rack_logger"
|
@@ -10,6 +21,7 @@ module Hanami
|
|
10
21
|
Dry::Monitor.load_extensions(:rack)
|
11
22
|
end
|
12
23
|
|
24
|
+
# @api private
|
13
25
|
def start
|
14
26
|
target.start :logger
|
15
27
|
|
@@ -1,27 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hanami
|
4
|
+
# @api private
|
4
5
|
module Providers
|
6
|
+
# Provider source to register routes helper component in Hanami slices.
|
7
|
+
#
|
8
|
+
# @see Hanami::Slice::RoutesHelper
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 2.0.0
|
5
12
|
class Routes < Dry::System::Provider::Source
|
13
|
+
# @api private
|
6
14
|
def self.for_slice(slice)
|
7
15
|
Class.new(self) do |klass|
|
8
16
|
klass.instance_variable_set(:@slice, slice)
|
9
17
|
end
|
10
18
|
end
|
11
19
|
|
20
|
+
# @api private
|
12
21
|
def self.slice
|
13
22
|
@slice || Hanami.app
|
14
23
|
end
|
15
24
|
|
25
|
+
# @api private
|
16
26
|
def prepare
|
17
27
|
require "hanami/slice/routes_helper"
|
18
28
|
end
|
19
29
|
|
30
|
+
# @api private
|
20
31
|
def start
|
21
|
-
# Register a lazy instance of RoutesHelper to ensure we don't load prematurely
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# slice.
|
32
|
+
# Register a lazy instance of RoutesHelper to ensure we don't load prematurely load the
|
33
|
+
# router during the process of booting. This ensures the router's resolver can run strict
|
34
|
+
# action key checks once when it runs on a fully booted slice.
|
25
35
|
register :routes do
|
26
36
|
Hanami::Slice::RoutesHelper.new(self.class.slice.router)
|
27
37
|
end
|
data/lib/hanami/routes.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "constants"
|
4
|
+
require_relative "errors"
|
4
5
|
|
5
6
|
module Hanami
|
6
7
|
# App routes
|
@@ -25,6 +26,40 @@ module Hanami
|
|
25
26
|
# @see Hanami::Slice::Router
|
26
27
|
# @since 2.0.0
|
27
28
|
class Routes
|
29
|
+
# Error raised when no action could be found in an app or slice container for the key given in a
|
30
|
+
# routes file.
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
# @since 2.0.0
|
34
|
+
class MissingActionError < Hanami::Error
|
35
|
+
# @api private
|
36
|
+
def initialize(action_key, slice)
|
37
|
+
action_path = action_key.gsub(CONTAINER_KEY_DELIMITER, PATH_DELIMITER)
|
38
|
+
action_constant = slice.inflector.camelize(
|
39
|
+
"#{slice.inflector.underscore(slice.namespace.to_s)}#{PATH_DELIMITER}#{action_path}"
|
40
|
+
)
|
41
|
+
action_file = slice.root.join("#{action_path}#{RB_EXT}")
|
42
|
+
|
43
|
+
super(<<~MSG)
|
44
|
+
Could not find action with key #{action_key.inspect} in #{slice}
|
45
|
+
|
46
|
+
To fix this, define the action class #{action_constant} in #{action_file}
|
47
|
+
MSG
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Error raised when a given routes endpoint does not implement the `#call` interface required
|
52
|
+
# for Rack.
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
# @since 2.0.0
|
56
|
+
class NotCallableEndpointError < Hanami::Error
|
57
|
+
# @api private
|
58
|
+
def initialize(endpoint)
|
59
|
+
super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
28
63
|
# @api private
|
29
64
|
def self.routes
|
30
65
|
@routes ||= build_routes
|
@@ -4,7 +4,7 @@ require "dry/core/constants"
|
|
4
4
|
|
5
5
|
module Hanami
|
6
6
|
class Settings
|
7
|
-
# The default store for {Hanami
|
7
|
+
# The default store for {Hanami::Settings}, loading setting values from `ENV`.
|
8
8
|
#
|
9
9
|
# If your app loads the dotenv gem, then `ENV` will also be populated from various `.env` files when
|
10
10
|
# you subclass `Hanami::App`.
|
data/lib/hanami/settings.rb
CHANGED
@@ -1,43 +1,95 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/core"
|
3
|
+
require "dry/core/constants"
|
4
4
|
require "dry/configurable"
|
5
|
+
require_relative "errors"
|
5
6
|
|
6
7
|
module Hanami
|
7
|
-
#
|
8
|
+
# Provides user-defined settings for an Hanami app or slice.
|
8
9
|
#
|
9
|
-
#
|
10
|
+
# Define your own settings by inheriting from this class in `config/settings.rb` within an app or
|
11
|
+
# slice. Your settings will be loaded from matching ENV vars (with upper-cased names) and made
|
12
|
+
# registered as a component as part of the Hanami app {Hanami::Slice::ClassMethods#prepare
|
13
|
+
# prepare} step.
|
14
|
+
#
|
15
|
+
# The settings instance is registered in your app and slice containers as a `"settings"`
|
16
|
+
# component. You can use the `Deps` mixin to inject this dependency and make settings available to
|
17
|
+
# your other components as required.
|
10
18
|
#
|
11
19
|
# @example
|
12
20
|
# # config/settings.rb
|
13
21
|
# # frozen_string_literal: true
|
14
22
|
#
|
15
|
-
# require "hanami/settings"
|
16
|
-
# require "my_app/types"
|
17
|
-
#
|
18
23
|
# module MyApp
|
19
24
|
# class Settings < Hanami::Settings
|
20
|
-
#
|
21
|
-
#
|
25
|
+
# Secret = Types::String.constrained(min_size: 20)
|
26
|
+
#
|
27
|
+
# setting :database_url, constructor: Types::String
|
28
|
+
# setting :session_secret, constructor: Secret
|
29
|
+
# setting :some_flag, default: false, constructor: Types::Params::Bool
|
22
30
|
# end
|
23
31
|
# end
|
24
32
|
#
|
25
|
-
# Settings are defined with [dry-configurable]
|
26
|
-
#
|
33
|
+
# Settings are defined with [dry-configurable][dry-c]'s `setting` method. You may likely want to
|
34
|
+
# provide `default:` and `constructor:` options for your settings.
|
35
|
+
#
|
36
|
+
# If you have [dry-types][dry-t] bundled, then a nested `Types` module will be available for type
|
37
|
+
# checking your setting values. Pass type objects to the setting `constructor:` options to ensure
|
38
|
+
# their values meet your type expectations. You can use dry-types' default type objects or define
|
39
|
+
# your own.
|
27
40
|
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
# {Hanami::
|
41
|
+
# When the settings are initialized, all type errors will be collected and presented together for
|
42
|
+
# correction. Settings are loaded early, as part of the Hanami app's
|
43
|
+
# {Hanami::Slice::ClassMethods#prepare prepare} step, to ensure that the app boots only when valid
|
44
|
+
# settings are present.
|
31
45
|
#
|
32
|
-
#
|
33
|
-
#
|
46
|
+
# Setting values are loaded from a configurable store, which defaults to
|
47
|
+
# {Hanami::Settings::EnvStore}, which fetches the values from equivalent upper-cased keys in
|
48
|
+
# `ENV`. You can configue an alternative store via {Hanami::Config#settings_store}. Setting stores
|
49
|
+
# must implement a `#fetch` method with the same signature as `Hash#fetch`.
|
50
|
+
#
|
51
|
+
# [dry-c]: https://dry-rb.org/gems/dry-configurable/
|
52
|
+
# [dry-t]: https://dry-rb.org/gems/dry-types/
|
34
53
|
#
|
35
54
|
# @see Hanami::Settings::DotenvStore
|
36
55
|
#
|
37
56
|
# @api public
|
38
57
|
# @since 2.0.0
|
39
58
|
class Settings
|
59
|
+
# Error raised when setting values do not meet their type expectations.
|
60
|
+
#
|
61
|
+
# Its message collects all the individual errors that can be raised for each setting.
|
62
|
+
#
|
63
|
+
# @api public
|
64
|
+
# @since 2.0.0
|
65
|
+
class InvalidSettingsError < Hanami::Error
|
66
|
+
# @api private
|
67
|
+
def initialize(errors)
|
68
|
+
super()
|
69
|
+
@errors = errors
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the exception's message.
|
73
|
+
#
|
74
|
+
# @return [String]
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
# @since 2.0.0
|
78
|
+
def to_s
|
79
|
+
<<~STR.strip
|
80
|
+
Could not initialize settings. The following settings were invalid:
|
81
|
+
|
82
|
+
#{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
|
83
|
+
STR
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
40
87
|
class << self
|
88
|
+
# Defines a nested `Types` constant in `Settings` subclasses if dry-types is bundled.
|
89
|
+
#
|
90
|
+
# @see https://dry-rb.org/gems/dry-types
|
91
|
+
#
|
92
|
+
# @api private
|
41
93
|
def inherited(subclass)
|
42
94
|
super
|
43
95
|
|
@@ -94,25 +146,6 @@ module Hanami
|
|
94
146
|
end
|
95
147
|
end
|
96
148
|
|
97
|
-
# Exception for errors in the definition of settings.
|
98
|
-
#
|
99
|
-
# Its message collects all the individual errors that can be raised for each setting.
|
100
|
-
#
|
101
|
-
# @api public
|
102
|
-
InvalidSettingsError = Class.new(StandardError) do
|
103
|
-
def initialize(errors)
|
104
|
-
@errors = errors
|
105
|
-
end
|
106
|
-
|
107
|
-
def to_s
|
108
|
-
<<~STR.strip
|
109
|
-
Could not initialize settings. The following settings were invalid:
|
110
|
-
|
111
|
-
#{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
|
112
|
-
STR
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
149
|
# @api private
|
117
150
|
Undefined = Dry::Core::Constants::Undefined
|
118
151
|
|
@@ -143,14 +176,47 @@ module Hanami
|
|
143
176
|
config.finalize!
|
144
177
|
end
|
145
178
|
|
179
|
+
# Returns a string containing a human-readable representation of the settings.
|
180
|
+
#
|
181
|
+
# This includes setting names only, not any values, to ensure that sensitive values do not
|
182
|
+
# inadvertently leak.
|
183
|
+
#
|
184
|
+
# Use {#inspect_values} to inspect settings with their values.
|
185
|
+
#
|
186
|
+
# @example
|
187
|
+
# settings.inspect
|
188
|
+
# # => #<MyApp::Settings [database_url, session_secret, some_flag]>
|
189
|
+
#
|
190
|
+
# @return [String]
|
191
|
+
#
|
192
|
+
# @see #inspect_values
|
193
|
+
#
|
194
|
+
# @api public
|
195
|
+
# @since 2.0.0
|
146
196
|
def inspect
|
147
|
-
"#<#{self.class
|
197
|
+
"#<#{self.class} [#{config._settings.map(&:name).join(", ")}]>"
|
148
198
|
end
|
149
199
|
|
200
|
+
# rubocop:disable Layout/LineLength
|
201
|
+
|
202
|
+
# Returns a string containing a human-readable representation of the settings and their values.
|
203
|
+
#
|
204
|
+
# @example
|
205
|
+
# settings.inspect_values
|
206
|
+
# # => #<MyApp::Settings database_url="postgres://localhost/my_db", session_secret="xxx", some_flag=true]>
|
207
|
+
#
|
208
|
+
# @return [String]
|
209
|
+
#
|
210
|
+
# @see #inspect
|
211
|
+
#
|
212
|
+
# @api public
|
213
|
+
# @since 2.0.0
|
150
214
|
def inspect_values
|
151
|
-
"#<#{self.class
|
215
|
+
"#<#{self.class} #{config._settings.map { |setting| "#{setting.name}=#{config[setting.name].inspect}" }.join(" ")}>"
|
152
216
|
end
|
153
217
|
|
218
|
+
# rubocop:enable Layout/LineLength
|
219
|
+
|
154
220
|
private
|
155
221
|
|
156
222
|
def method_missing(name, *args, &block)
|