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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/hanami.gemspec +8 -7
  4. data/lib/hanami/app.rb +47 -36
  5. data/lib/hanami/assets/app_config.rb +7 -15
  6. data/lib/hanami/assets/config.rb +5 -6
  7. data/lib/hanami/config/actions/content_security_policy.rb +1 -1
  8. data/lib/hanami/config/actions/cookies.rb +27 -0
  9. data/lib/hanami/config/actions/sessions.rb +42 -5
  10. data/lib/hanami/config/actions.rb +81 -17
  11. data/lib/hanami/config/logger.rb +112 -23
  12. data/lib/hanami/config/router.rb +0 -1
  13. data/lib/hanami/config/views.rb +6 -10
  14. data/lib/hanami/config.rb +235 -73
  15. data/lib/hanami/constants.rb +4 -0
  16. data/lib/hanami/errors.rb +17 -0
  17. data/lib/hanami/extensions/action/slice_configured_action.rb +9 -5
  18. data/lib/hanami/extensions/action.rb +59 -7
  19. data/lib/hanami/extensions/view/context.rb +3 -4
  20. data/lib/hanami/extensions/view/slice_configured_view.rb +4 -4
  21. data/lib/hanami/extensions/view.rb +7 -5
  22. data/lib/hanami/providers/inflector.rb +6 -0
  23. data/lib/hanami/providers/logger.rb +8 -0
  24. data/lib/hanami/providers/rack.rb +12 -0
  25. data/lib/hanami/providers/routes.rb +14 -4
  26. data/lib/hanami/routes.rb +36 -1
  27. data/lib/hanami/settings/env_store.rb +1 -1
  28. data/lib/hanami/settings.rb +102 -36
  29. data/lib/hanami/slice/router.rb +38 -16
  30. data/lib/hanami/slice/routing/middleware/stack.rb +66 -42
  31. data/lib/hanami/slice/routing/resolver.rb +10 -17
  32. data/lib/hanami/slice/view_name_inferrer.rb +1 -1
  33. data/lib/hanami/slice.rb +553 -14
  34. data/lib/hanami/slice_registrar.rb +20 -15
  35. data/lib/hanami/version.rb +2 -3
  36. data/lib/hanami/web/rack_logger.rb +14 -4
  37. data/lib/hanami.rb +122 -23
  38. data/spec/integration/action/csrf_protection_spec.rb +1 -1
  39. data/spec/integration/container/application_routes_helper_spec.rb +3 -1
  40. data/spec/integration/container/provider_lifecycle_spec.rb +61 -0
  41. data/spec/integration/container/standard_providers/rack_provider_spec.rb +44 -0
  42. data/spec/integration/container/{standard_bootable_components_spec.rb → standard_providers_spec.rb} +3 -3
  43. data/spec/integration/rack_app/body_parser_spec.rb +3 -0
  44. data/spec/integration/rack_app/middleware_spec.rb +427 -3
  45. data/spec/integration/rack_app/non_booted_rack_app_spec.rb +2 -1
  46. data/spec/integration/rack_app/rack_app_spec.rb +39 -11
  47. data/spec/integration/setup_spec.rb +4 -4
  48. data/spec/integration/slices/external_slice_spec.rb +2 -1
  49. data/spec/integration/slices/slice_configuration_spec.rb +3 -1
  50. data/spec/integration/slices/slice_loading_spec.rb +4 -4
  51. data/spec/integration/slices/slice_routing_spec.rb +4 -3
  52. data/spec/integration/slices_spec.rb +100 -0
  53. data/spec/isolation/hanami/boot/success_spec.rb +1 -1
  54. data/spec/support/app_integration.rb +2 -10
  55. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +7 -7
  56. data/spec/unit/hanami/config/actions/default_values_spec.rb +1 -1
  57. data/spec/unit/hanami/config/actions/sessions_spec.rb +1 -3
  58. data/spec/unit/hanami/config/actions_spec.rb +1 -12
  59. data/spec/unit/hanami/config/logger_spec.rb +38 -55
  60. data/spec/unit/hanami/config/router_spec.rb +1 -1
  61. data/spec/unit/hanami/config/views_spec.rb +3 -13
  62. data/spec/unit/hanami/settings_spec.rb +1 -1
  63. data/spec/unit/hanami/slice_configurable_spec.rb +5 -5
  64. data/spec/unit/hanami/slice_spec.rb +32 -0
  65. data/spec/unit/hanami/version_spec.rb +1 -1
  66. data/spec/unit/hanami/web/rack_logger_spec.rb +13 -2
  67. metadata +54 -45
  68. data/lib/hanami/config/sessions.rb +0 -50
  69. 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(:"#{setting}")
90
- parent_value = slice.parent.config.actions.public_send(:"#{setting}") if slice.parent
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
- # Extended behavior for actions intended for use within an Hanami app.
8
+ # Integrated behavior for `Hanami::Action` classes within Hanami apps.
10
9
  #
11
- # @see Hanami::Action
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
- attr_reader :view, :view_context, :routes
39
+ # @api private
40
+ attr_reader :view
32
41
 
33
- def initialize(view: nil, view_context: nil, routes: nil, **kwargs)
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/disable automatic rendering.
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
- # @api public
15
- # @since 2.0.0
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?(:"#{setting}")
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(:"#{setting}")
76
- parent_value = slice.parent.config.views.public_send(:"#{setting}") if slice.parent
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
- # Extended behavior for actions intended for use within an Hanami app.
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 public
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
- # load the router during the process of booting. This ensures the router's
23
- # resolver can run strict action key checks once when it runs on a fully booted
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
- require "hanami/slice/router"
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:Settings}, loading setting values from `ENV`.
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`.
@@ -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
- # App settings
8
+ # Provides user-defined settings for an Hanami app or slice.
8
9
  #
9
- # Users are expected to inherit from this class to define their app settings.
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
- # setting :database_url
21
- # setting :feature_flag, default: false, constructor: Types::Params::Bool
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](https://dry-rb.org/gems/dry-configurable/), so you
26
- # can take a look there to see the supported syntax.
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
- # Users work with an instance of this class made available within the `settings` key in the
29
- # container. The instance gets its settings populated from a configurable store, which defaults to
30
- # {Hanami::Settings::EnvStore}.
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
- # A different store can be set through the `settings_store` Hanami configuration option. All it
33
- # needs to do is implementing a `#fetch` method with the same signature as `Hash#fetch`.
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.to_s} [#{config._settings.map(&:name).join(", ")}]>"
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.to_s} #{config._settings.map { |setting| "#{setting.name}=#{config[setting.name].inspect}" }.join(" ")}>"
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)