hanami 2.0.0.alpha6 → 2.0.0.alpha8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "hanami/action/configuration"
5
+ require_relative "actions/cookies"
6
+ require_relative "actions/sessions"
7
+ require_relative "actions/content_security_policy"
8
+ require_relative "../application/view_name_inferrer"
9
+
10
+ module Hanami
11
+ class Configuration
12
+ # Hanami actions configuration
13
+ #
14
+ # @since 2.0.0
15
+ class Actions
16
+ include Dry::Configurable
17
+
18
+ setting :cookies, default: {}, constructor: -> options { Cookies.new(options) }
19
+ setting :sessions, constructor: proc { |storage, *options| Sessions.new(storage, *options) }
20
+ setting :csrf_protection
21
+
22
+ setting :name_inference_base, default: "actions"
23
+ setting :view_context_identifier, default: "view.context"
24
+ setting :view_name_inferrer, default: Application::ViewNameInferrer
25
+ setting :view_name_inference_base, default: "views"
26
+
27
+ attr_accessor :content_security_policy
28
+
29
+ attr_reader :base_configuration
30
+ private :base_configuration
31
+
32
+ def initialize(*, **options)
33
+ super()
34
+
35
+ @base_configuration = Hanami::Action::Configuration.new
36
+ @content_security_policy = ContentSecurityPolicy.new do |csp|
37
+ if assets_server_url = options[:assets_server_url]
38
+ csp[:script_src] += " #{assets_server_url}"
39
+ csp[:style_src] += " #{assets_server_url}"
40
+ end
41
+ end
42
+
43
+ configure_defaults
44
+ end
45
+
46
+ def finalize!
47
+ # A nil value for `csrf_protection` means it has not been explicitly configured
48
+ # (neither true nor false), so we can default it to whether sessions are enabled
49
+ self.csrf_protection = sessions.enabled? if csrf_protection.nil?
50
+
51
+ if content_security_policy
52
+ default_headers["Content-Security-Policy"] = content_security_policy.to_str
53
+ end
54
+ end
55
+
56
+ # Returns the list of available settings
57
+ #
58
+ # @return [Set]
59
+ #
60
+ # @since 2.0.0
61
+ # @api private
62
+ def settings
63
+ base_configuration.settings + self.class.settings
64
+ end
65
+
66
+ private
67
+
68
+ # Apply defaults for base configuration settings
69
+ def configure_defaults
70
+ self.default_request_format = :html
71
+ self.default_response_format = :html
72
+
73
+ self.default_headers = {
74
+ "X-Frame-Options" => "DENY",
75
+ "X-Content-Type-Options" => "nosniff",
76
+ "X-XSS-Protection" => "1; mode=block"
77
+ }
78
+ end
79
+
80
+ def method_missing(name, *args, &block)
81
+ if config.respond_to?(name)
82
+ config.public_send(name, *args, &block)
83
+ elsif base_configuration.respond_to?(name)
84
+ base_configuration.public_send(name, *args, &block)
85
+ else
86
+ super
87
+ end
88
+ end
89
+
90
+ def respond_to_missing?(name, _incude_all = false)
91
+ config.respond_to?(name) || base_configuration.respond_to?(name) || super
92
+ end
93
+ end
94
+ end
95
+ end
@@ -11,9 +11,9 @@ module Hanami
11
11
  class Logger
12
12
  include Dry::Configurable
13
13
 
14
- protected :config
14
+ attr_reader :application_name
15
15
 
16
- setting :application_name
16
+ protected :config
17
17
 
18
18
  setting :level
19
19
 
@@ -30,7 +30,6 @@ module Hanami
30
30
  setting :logger_class, default: Hanami::Logger
31
31
 
32
32
  def initialize(env:, application_name:)
33
- @env = env
34
33
  @application_name = application_name
35
34
 
36
35
  config.level = case env
@@ -58,12 +57,16 @@ module Hanami
58
57
  end
59
58
  end
60
59
 
61
- def finalize!
62
- config.application_name = @application_name.call
63
- end
64
-
65
60
  def instance
66
- logger_class.new(application_name, *options, stream: stream, level: level, formatter: formatter, filter: filters, colorizer: colors)
61
+ logger_class.new(
62
+ application_name.name,
63
+ *options,
64
+ stream: stream,
65
+ level: level,
66
+ formatter: formatter,
67
+ filter: filters,
68
+ colorizer: colors
69
+ )
67
70
  end
68
71
 
69
72
  private
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "hanami/view"
5
+
6
+ module Hanami
7
+ class Configuration
8
+ # Hanami actions configuration
9
+ #
10
+ # @since 2.0.0
11
+ class Views
12
+ include Dry::Configurable
13
+
14
+ setting :parts_path, default: "view/parts"
15
+
16
+ attr_reader :base_configuration
17
+ private :base_configuration
18
+
19
+ def initialize(*)
20
+ super
21
+
22
+ @base_configuration = Hanami::View.config.dup
23
+
24
+ configure_defaults
25
+ end
26
+
27
+ # Returns the list of available settings
28
+ #
29
+ # @return [Set]
30
+ #
31
+ # @since 2.0.0
32
+ # @api private
33
+ def settings
34
+ self.class.settings + View.settings - NON_FORWARDABLE_METHODS
35
+ end
36
+
37
+ def finalize!
38
+ return self if frozen?
39
+
40
+ base_configuration.finalize!
41
+
42
+ super
43
+ end
44
+
45
+ private
46
+
47
+ def configure_defaults
48
+ self.paths = ["templates"]
49
+ self.template_inference_base = "views"
50
+ self.layout = "application"
51
+ end
52
+
53
+ # An inflector for views is not configurable via `config.views.inflector` on an
54
+ # `Hanami::Application`. The application-wide inflector is already configurable
55
+ # there as `config.inflector` and will be used as the default inflector for views.
56
+ #
57
+ # A custom inflector may still be provided in an `Hanami::View` subclass, via
58
+ # `config.inflector=`.
59
+ NON_FORWARDABLE_METHODS = %i[inflector inflector=].freeze
60
+ private_constant :NON_FORWARDABLE_METHODS
61
+
62
+ def method_missing(name, *args, &block)
63
+ return super if NON_FORWARDABLE_METHODS.include?(name)
64
+
65
+ if config.respond_to?(name)
66
+ config.public_send(name, *args, &block)
67
+ elsif base_configuration.respond_to?(name)
68
+ base_configuration.public_send(name, *args, &block)
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ def respond_to_missing?(name, _include_all = false)
75
+ return false if NON_FORWARDABLE_METHODS.include?(name)
76
+
77
+ config.respond_to?(name) || base_configuration.respond_to?(name) || super
78
+ end
79
+ end
80
+ end
81
+ end
@@ -13,21 +13,20 @@ require_relative "configuration/middleware"
13
13
  require_relative "configuration/router"
14
14
  require_relative "configuration/sessions"
15
15
  require_relative "configuration/source_dirs"
16
+ require_relative "constants"
16
17
 
17
18
  module Hanami
18
19
  # Hanami application configuration
19
20
  #
20
21
  # @since 2.0.0
21
- #
22
- # rubocop:disable Metrics/ClassLength
23
22
  class Configuration
24
23
  include Dry::Configurable
25
24
 
26
25
  DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
27
26
  private_constant :DEFAULT_ENVIRONMENTS
28
27
 
29
- MODULE_DELIMITER = "::"
30
- private_constant :MODULE_DELIMITER
28
+ attr_reader :application_name
29
+ attr_reader :env
31
30
 
32
31
  attr_reader :actions
33
32
  attr_reader :middleware
@@ -37,57 +36,41 @@ module Hanami
37
36
  attr_reader :environments
38
37
  private :environments
39
38
 
39
+ # rubocop:disable Metrics/AbcSize
40
40
  def initialize(application_name:, env:)
41
- @namespace = application_name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER)
41
+ @application_name = application_name
42
42
 
43
43
  @environments = DEFAULT_ENVIRONMENTS.clone
44
- config.env = env
44
+ @env = env
45
45
 
46
46
  # Some default setting values must be assigned at initialize-time to ensure they
47
47
  # have appropriate values for the current application
48
48
  self.root = Dir.pwd
49
49
  self.settings_store = Application::Settings::DotenvStore.new.with_dotenv_loaded
50
50
 
51
- config.logger = Configuration::Logger.new(env: env, application_name: method(:application_name))
51
+ config.logger = Configuration::Logger.new(env: env, application_name: application_name)
52
52
 
53
- @assets = begin
54
- require_path = "hanami/assets/application_configuration"
55
- require require_path
53
+ @assets = load_dependent_config("hanami/assets/application_configuration") {
56
54
  Hanami::Assets::ApplicationConfiguration.new
57
- rescue LoadError => e
58
- raise e unless e.path == require_path
59
- require_relative "configuration/null_configuration"
60
- NullConfiguration.new
61
- end
55
+ }
62
56
 
63
- # Config for actions (same for views, below) may not be available if the gem isn't
64
- # loaded; fall back to a null config object if it's missing
65
- @actions = begin
66
- require_path = "hanami/action/application_configuration"
67
- require require_path
68
- Hanami::Action::ApplicationConfiguration.new(assets_server_url: assets.server_url)
69
- rescue LoadError => e
70
- raise e unless e.path == require_path
71
- require_relative "configuration/null_configuration"
72
- NullConfiguration.new
73
- end
57
+ @actions = load_dependent_config("hanami/action") {
58
+ require_relative "configuration/actions"
59
+ Actions.new
60
+ }
74
61
 
75
62
  @middleware = Middleware.new
76
63
 
77
64
  @router = Router.new(self)
78
65
 
79
- @views = begin
80
- require_path = "hanami/view/application_configuration"
81
- require require_path
82
- Hanami::View::ApplicationConfiguration.new
83
- rescue LoadError => e
84
- raise e unless e.path == require_path
85
- require_relative "configuration/null_configuration"
86
- NullConfiguration.new
87
- end
66
+ @views = load_dependent_config("hanami/view") {
67
+ require_relative "configuration/views"
68
+ Views.new
69
+ }
88
70
 
89
71
  yield self if block_given?
90
72
  end
73
+ # rubocop:enable Metrics/AbcSize
91
74
 
92
75
  def environment(env_name, &block)
93
76
  environments[env_name] << block
@@ -109,21 +92,6 @@ module Hanami
109
92
  super
110
93
  end
111
94
 
112
- def namespace
113
- inflector.constantize(@namespace)
114
- end
115
-
116
- def application_name
117
- inflector.underscore(@namespace).to_sym
118
- end
119
-
120
- setting :env
121
-
122
- def env=(new_env)
123
- config.env = env
124
- apply_env_config(new_env)
125
- end
126
-
127
95
  setting :root, constructor: -> path { Pathname(path) }
128
96
 
129
97
  setting :inflector, default: Dry::Inflector.new
@@ -148,20 +116,8 @@ module Hanami
148
116
 
149
117
  setting :settings_store, default: Application::Settings::DotenvStore
150
118
 
151
- setting :slices_dir, default: "slices"
152
-
153
- setting :slices_namespace, default: Object
154
-
155
- # TODO: convert into a dedicated object with explicit behaviour around blocks per
156
- # slice, etc.
157
- setting :slices, default: {}, constructor: :dup.to_proc
158
-
159
119
  setting :source_dirs, default: Configuration::SourceDirs.new, cloneable: true
160
120
 
161
- def slice(slice_name, &block)
162
- slices[slice_name] = block
163
- end
164
-
165
121
  setting :base_url, default: "http://0.0.0.0:2300", constructor: -> url { URI(url) }
166
122
 
167
123
  def for_each_middleware(&blk)
@@ -181,6 +137,16 @@ module Hanami
181
137
  end
182
138
  end
183
139
 
140
+ def load_dependent_config(require_path, &block)
141
+ require require_path
142
+ yield
143
+ rescue LoadError => e
144
+ raise e unless e.path == require_path
145
+
146
+ require_relative "configuration/null_configuration"
147
+ NullConfiguration.new
148
+ end
149
+
184
150
  def method_missing(name, *args, &block)
185
151
  if config.respond_to?(name)
186
152
  config.public_send(name, *args, &block)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ CONTAINER_KEY_DELIMITER = "."
5
+ private_constant :CONTAINER_KEY_DELIMITER
6
+
7
+ # @api private
8
+ MODULE_DELIMITER = "::"
9
+ private_constant :MODULE_DELIMITER
10
+
11
+ PATH_DELIMITER = "/"
12
+ private_constant :PATH_DELIMITER
13
+
14
+ # @api private
15
+ CONFIG_DIR = "config"
16
+ private_constant :CONFIG_DIR
17
+
18
+ # @api private
19
+ SLICES_DIR = "slices"
20
+ private_constant :SLICES_DIR
21
+
22
+ # @api private
23
+ LIB_DIR = "lib"
24
+ private_constant :LIB_DIR
25
+
26
+ # @api private
27
+ RB_EXT = ".rb"
28
+ private_constant :RB_EXT
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ # @since 2.0.0
5
+ Error = Class.new(StandardError)
6
+
7
+ # @since 2.0.0
8
+ ApplicationLoadError = Class.new(Error)
9
+
10
+ # @since 2.0.0
11
+ SliceLoadError = Class.new(Error)
12
+
13
+ # @since 2.0.0
14
+ ComponentLoadError = Class.new(Error)
15
+ end