hanami 2.0.0.beta4 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|