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.
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)