hanami 2.0.0.alpha7.1 → 2.0.0.beta1.1

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 (223) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +457 -237
  3. data/FEATURES.md +30 -9
  4. data/README.md +1 -3
  5. data/hanami.gemspec +21 -11
  6. data/lib/hanami/app.rb +141 -0
  7. data/lib/hanami/assets/application_configuration.rb +10 -4
  8. data/lib/hanami/configuration/actions/content_security_policy.rb +118 -0
  9. data/lib/hanami/configuration/actions/cookies.rb +29 -0
  10. data/lib/hanami/configuration/actions/sessions.rb +46 -0
  11. data/lib/hanami/configuration/actions.rb +23 -12
  12. data/lib/hanami/configuration/logger.rb +13 -10
  13. data/lib/hanami/configuration/router.rb +2 -6
  14. data/lib/hanami/configuration/sessions.rb +1 -1
  15. data/lib/hanami/configuration/views.rb +86 -0
  16. data/lib/hanami/configuration.rb +139 -82
  17. data/lib/hanami/constants.rb +30 -2
  18. data/lib/hanami/errors.rb +4 -1
  19. data/lib/hanami/extensions/action/slice_configured_action.rb +103 -0
  20. data/lib/hanami/extensions/action.rb +79 -0
  21. data/lib/hanami/extensions/view/context.rb +106 -0
  22. data/lib/hanami/extensions/view/slice_configured_context.rb +71 -0
  23. data/lib/hanami/extensions/view/slice_configured_view.rb +107 -0
  24. data/lib/hanami/extensions/view.rb +33 -0
  25. data/lib/hanami/extensions.rb +10 -0
  26. data/lib/hanami/providers/inflector.rb +13 -0
  27. data/lib/hanami/providers/logger.rb +13 -0
  28. data/lib/hanami/providers/rack.rb +27 -0
  29. data/lib/hanami/providers/routes.rb +33 -0
  30. data/lib/hanami/providers/settings.rb +23 -0
  31. data/lib/hanami/rake_tasks.rb +61 -0
  32. data/lib/hanami/routes.rb +51 -0
  33. data/lib/hanami/server.rb +1 -1
  34. data/lib/hanami/settings/dotenv_store.rb +58 -0
  35. data/lib/hanami/settings.rb +90 -0
  36. data/lib/hanami/setup.rb +4 -2
  37. data/lib/hanami/{application → slice}/router.rb +18 -13
  38. data/lib/hanami/slice/routes_helper.rb +37 -0
  39. data/lib/hanami/{application → slice}/routing/middleware/stack.rb +43 -5
  40. data/lib/hanami/slice/routing/resolver.rb +97 -0
  41. data/lib/hanami/slice/view_name_inferrer.rb +63 -0
  42. data/lib/hanami/slice.rb +252 -82
  43. data/lib/hanami/slice_configurable.rb +62 -0
  44. data/lib/hanami/slice_name.rb +111 -0
  45. data/lib/hanami/slice_registrar.rb +119 -0
  46. data/lib/hanami/version.rb +1 -1
  47. data/lib/hanami/web/rack_logger.rb +1 -1
  48. data/lib/hanami.rb +34 -26
  49. data/spec/integration/application_middleware_stack_spec.rb +84 -0
  50. data/spec/integration/assets/cdn_spec.rb +48 -0
  51. data/spec/integration/assets/fingerprint_spec.rb +42 -0
  52. data/spec/integration/assets/helpers_spec.rb +50 -0
  53. data/spec/integration/assets/serve_spec.rb +70 -0
  54. data/spec/integration/assets/subresource_integrity_spec.rb +54 -0
  55. data/spec/integration/body_parsers_spec.rb +50 -0
  56. data/spec/integration/cli/assets/precompile_spec.rb +147 -0
  57. data/spec/integration/cli/assets_spec.rb +14 -0
  58. data/spec/integration/cli/console_spec.rb +105 -0
  59. data/spec/integration/cli/db/apply_spec.rb +74 -0
  60. data/spec/integration/cli/db/console_spec.rb +40 -0
  61. data/spec/integration/cli/db/create_spec.rb +50 -0
  62. data/spec/integration/cli/db/drop_spec.rb +54 -0
  63. data/spec/integration/cli/db/migrate_spec.rb +108 -0
  64. data/spec/integration/cli/db/prepare_spec.rb +36 -0
  65. data/spec/integration/cli/db/rollback_spec.rb +96 -0
  66. data/spec/integration/cli/db/version_spec.rb +38 -0
  67. data/spec/integration/cli/db_spec.rb +21 -0
  68. data/spec/integration/cli/destroy/action_spec.rb +143 -0
  69. data/spec/integration/cli/destroy/app_spec.rb +118 -0
  70. data/spec/integration/cli/destroy/mailer_spec.rb +74 -0
  71. data/spec/integration/cli/destroy/migration_spec.rb +70 -0
  72. data/spec/integration/cli/destroy/model_spec.rb +113 -0
  73. data/spec/integration/cli/destroy_spec.rb +18 -0
  74. data/spec/integration/cli/generate/action_spec.rb +469 -0
  75. data/spec/integration/cli/generate/app_spec.rb +215 -0
  76. data/spec/integration/cli/generate/mailer_spec.rb +189 -0
  77. data/spec/integration/cli/generate/migration_spec.rb +72 -0
  78. data/spec/integration/cli/generate/model_spec.rb +290 -0
  79. data/spec/integration/cli/generate/secret_spec.rb +56 -0
  80. data/spec/integration/cli/generate_spec.rb +19 -0
  81. data/spec/integration/cli/new/database_spec.rb +235 -0
  82. data/spec/integration/cli/new/hanami_head_spec.rb +27 -0
  83. data/spec/integration/cli/new/template_spec.rb +118 -0
  84. data/spec/integration/cli/new/test_spec.rb +274 -0
  85. data/spec/integration/cli/new_spec.rb +970 -0
  86. data/spec/integration/cli/plugins_spec.rb +39 -0
  87. data/spec/integration/cli/routes_spec.rb +49 -0
  88. data/spec/integration/cli/server_spec.rb +626 -0
  89. data/spec/integration/cli/version_spec.rb +85 -0
  90. data/spec/integration/early_hints_spec.rb +35 -0
  91. data/spec/integration/handle_exceptions_spec.rb +244 -0
  92. data/spec/integration/head_spec.rb +89 -0
  93. data/spec/integration/http_headers_spec.rb +29 -0
  94. data/spec/integration/mailer_spec.rb +32 -0
  95. data/spec/integration/middleware_spec.rb +81 -0
  96. data/spec/integration/mount_applications_spec.rb +88 -0
  97. data/spec/integration/project_initializers_spec.rb +40 -0
  98. data/spec/integration/rackup_spec.rb +35 -0
  99. data/spec/integration/rake/with_minitest_spec.rb +67 -0
  100. data/spec/integration/rake/with_rspec_spec.rb +69 -0
  101. data/spec/integration/routing_helpers_spec.rb +61 -0
  102. data/spec/integration/security/content_security_policy_spec.rb +46 -0
  103. data/spec/integration/security/csrf_protection_spec.rb +42 -0
  104. data/spec/integration/security/force_ssl_spec.rb +29 -0
  105. data/spec/integration/security/x_content_type_options_spec.rb +46 -0
  106. data/spec/integration/security/x_frame_options_spec.rb +46 -0
  107. data/spec/integration/security/x_xss_protection_spec.rb +46 -0
  108. data/spec/integration/send_file_spec.rb +51 -0
  109. data/spec/integration/sessions_spec.rb +247 -0
  110. data/spec/integration/static_middleware_spec.rb +21 -0
  111. data/spec/integration/streaming_spec.rb +41 -0
  112. data/spec/integration/unsafe_send_file_spec.rb +52 -0
  113. data/spec/isolation/hanami/application/already_configured_spec.rb +19 -0
  114. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +10 -0
  115. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +14 -0
  116. data/spec/isolation/hanami/application/not_configured_spec.rb +9 -0
  117. data/spec/isolation/hanami/application/routes/configured_spec.rb +44 -0
  118. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +16 -0
  119. data/spec/isolation/hanami/boot/success_spec.rb +50 -0
  120. data/spec/new_integration/action/configuration_spec.rb +26 -0
  121. data/spec/new_integration/action/cookies_spec.rb +58 -0
  122. data/spec/new_integration/action/csrf_protection_spec.rb +54 -0
  123. data/spec/new_integration/action/routes_spec.rb +73 -0
  124. data/spec/new_integration/action/sessions_spec.rb +50 -0
  125. data/spec/new_integration/action/view_integration_spec.rb +165 -0
  126. data/spec/new_integration/action/view_rendering/automatic_rendering_spec.rb +247 -0
  127. data/spec/new_integration/action/view_rendering/paired_view_inference_spec.rb +115 -0
  128. data/spec/new_integration/action/view_rendering_spec.rb +107 -0
  129. data/spec/new_integration/code_loading/loading_from_app_spec.rb +152 -0
  130. data/spec/new_integration/code_loading/loading_from_slice_spec.rb +165 -0
  131. data/spec/new_integration/container/application_routes_helper_spec.rb +48 -0
  132. data/spec/new_integration/container/auto_injection_spec.rb +53 -0
  133. data/spec/new_integration/container/auto_registration_spec.rb +86 -0
  134. data/spec/new_integration/container/autoloader_spec.rb +80 -0
  135. data/spec/new_integration/container/imports_spec.rb +253 -0
  136. data/spec/new_integration/container/prepare_container_spec.rb +123 -0
  137. data/spec/new_integration/container/shutdown_spec.rb +91 -0
  138. data/spec/new_integration/container/standard_bootable_components_spec.rb +124 -0
  139. data/spec/new_integration/rack_app/middleware_spec.rb +215 -0
  140. data/spec/new_integration/rack_app/non_booted_rack_app_spec.rb +105 -0
  141. data/spec/new_integration/rack_app/rack_app_spec.rb +524 -0
  142. data/spec/new_integration/settings_spec.rb +115 -0
  143. data/spec/new_integration/slices/external_slice_spec.rb +92 -0
  144. data/spec/new_integration/slices/slice_configuration_spec.rb +40 -0
  145. data/spec/new_integration/slices/slice_routing_spec.rb +226 -0
  146. data/spec/new_integration/slices/slice_settings_spec.rb +141 -0
  147. data/spec/new_integration/slices_spec.rb +101 -0
  148. data/spec/new_integration/view/configuration_spec.rb +49 -0
  149. data/spec/new_integration/view/context/assets_spec.rb +67 -0
  150. data/spec/new_integration/view/context/inflector_spec.rb +48 -0
  151. data/spec/new_integration/view/context/request_spec.rb +61 -0
  152. data/spec/new_integration/view/context/routes_spec.rb +86 -0
  153. data/spec/new_integration/view/context/settings_spec.rb +50 -0
  154. data/spec/new_integration/view/inflector_spec.rb +57 -0
  155. data/spec/new_integration/view/part_namespace_spec.rb +96 -0
  156. data/spec/new_integration/view/path_spec.rb +56 -0
  157. data/spec/new_integration/view/template_spec.rb +68 -0
  158. data/spec/new_integration/view/views_spec.rb +103 -0
  159. data/spec/spec_helper.rb +16 -0
  160. data/spec/support/app_integration.rb +91 -0
  161. data/spec/support/coverage.rb +1 -0
  162. data/spec/support/fixtures/hanami-plugin/Gemfile +8 -0
  163. data/spec/support/fixtures/hanami-plugin/README.md +35 -0
  164. data/spec/support/fixtures/hanami-plugin/Rakefile +4 -0
  165. data/spec/support/fixtures/hanami-plugin/bin/console +15 -0
  166. data/spec/support/fixtures/hanami-plugin/bin/setup +8 -0
  167. data/spec/support/fixtures/hanami-plugin/hanami-plugin.gemspec +28 -0
  168. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/cli.rb +19 -0
  169. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/version.rb +7 -0
  170. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin.rb +8 -0
  171. data/spec/support/rspec.rb +27 -0
  172. data/spec/support/shared_examples/cli/generate/app.rb +494 -0
  173. data/spec/support/shared_examples/cli/generate/migration.rb +32 -0
  174. data/spec/support/shared_examples/cli/generate/model.rb +81 -0
  175. data/spec/support/shared_examples/cli/new.rb +97 -0
  176. data/spec/unit/hanami/configuration/actions/content_security_policy_spec.rb +102 -0
  177. data/spec/unit/hanami/configuration/actions/cookies_spec.rb +46 -0
  178. data/spec/unit/hanami/configuration/actions/csrf_protection_spec.rb +57 -0
  179. data/spec/unit/hanami/configuration/actions/default_values_spec.rb +52 -0
  180. data/spec/unit/hanami/configuration/actions/sessions_spec.rb +50 -0
  181. data/spec/unit/hanami/configuration/actions_spec.rb +78 -0
  182. data/spec/unit/hanami/configuration/base_url_spec.rb +25 -0
  183. data/spec/unit/hanami/configuration/inflector_spec.rb +35 -0
  184. data/spec/unit/hanami/configuration/logger_spec.rb +203 -0
  185. data/spec/unit/hanami/configuration/views_spec.rb +120 -0
  186. data/spec/unit/hanami/configuration_spec.rb +43 -0
  187. data/spec/unit/hanami/env_spec.rb +54 -0
  188. data/spec/unit/hanami/routes_spec.rb +25 -0
  189. data/spec/unit/hanami/settings/dotenv_store_spec.rb +119 -0
  190. data/spec/unit/hanami/settings_spec.rb +56 -0
  191. data/spec/unit/hanami/slice_configurable_spec.rb +104 -0
  192. data/spec/unit/hanami/slice_name_spec.rb +47 -0
  193. data/spec/unit/hanami/slice_spec.rb +17 -0
  194. data/spec/unit/hanami/version_spec.rb +7 -0
  195. data/spec/unit/hanami/web/rack_logger_spec.rb +78 -0
  196. metadata +363 -55
  197. data/lib/hanami/application/container/providers/inflector.rb +0 -7
  198. data/lib/hanami/application/container/providers/logger.rb +0 -7
  199. data/lib/hanami/application/container/providers/rack_logger.rb +0 -15
  200. data/lib/hanami/application/container/providers/rack_monitor.rb +0 -12
  201. data/lib/hanami/application/container/providers/routes_helper.rb +0 -9
  202. data/lib/hanami/application/container/providers/settings.rb +0 -7
  203. data/lib/hanami/application/routes.rb +0 -55
  204. data/lib/hanami/application/routes_helper.rb +0 -34
  205. data/lib/hanami/application/routing/resolver/node.rb +0 -50
  206. data/lib/hanami/application/routing/resolver/trie.rb +0 -59
  207. data/lib/hanami/application/routing/resolver.rb +0 -87
  208. data/lib/hanami/application/routing/router.rb +0 -36
  209. data/lib/hanami/application/settings/dotenv_store.rb +0 -60
  210. data/lib/hanami/application/settings.rb +0 -93
  211. data/lib/hanami/application/slice_registrar.rb +0 -106
  212. data/lib/hanami/application.rb +0 -299
  213. data/lib/hanami/boot/source_dirs.rb +0 -44
  214. data/lib/hanami/cli/application/cli.rb +0 -40
  215. data/lib/hanami/cli/application/command.rb +0 -47
  216. data/lib/hanami/cli/application/commands/console.rb +0 -81
  217. data/lib/hanami/cli/application/commands.rb +0 -16
  218. data/lib/hanami/cli/base_command.rb +0 -48
  219. data/lib/hanami/cli/commands/command.rb +0 -171
  220. data/lib/hanami/cli/commands/server.rb +0 -88
  221. data/lib/hanami/cli/commands.rb +0 -65
  222. data/lib/hanami/configuration/middleware.rb +0 -20
  223. data/lib/hanami/configuration/source_dirs.rb +0 -42
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "dry/core/constants"
5
+
6
+ module Hanami
7
+ # App settings
8
+ #
9
+ # Users are expected to inherit from this class to define their app settings.
10
+ #
11
+ # @example
12
+ # # config/settings.rb
13
+ # # frozen_string_literal: true
14
+ #
15
+ # require "hanami/settings"
16
+ # require "my_app/types"
17
+ #
18
+ # module MyApp
19
+ # class Settings < Hanami::Settings
20
+ # setting :database_url
21
+ # setting :feature_flag, default: false, constructor: Types::Params::Bool
22
+ # end
23
+ # end
24
+ #
25
+ # Settings are defined with
26
+ # [dry-configurable](https://dry-rb.org/gems/dry-configurable/), so you can
27
+ # take a look there to see the supported syntax.
28
+ #
29
+ # Users work with an instance of this class made available within the
30
+ # `settings` key in the container. The instance gets its settings populated
31
+ # from a configurable store, which defaults to
32
+ # {Hanami::Settings::DotenvStore}.
33
+ #
34
+ # A different store can be set through the `settings_store` Hanami
35
+ # configuration option. All it needs to do is implementing a `#fetch` method
36
+ # with the same signature as `Hash#fetch`.
37
+ #
38
+ # @see Hanami::Settings::DotenvStore
39
+ # @since 2.0.0
40
+ class Settings
41
+ # Exception for errors in the definition of settings.
42
+ #
43
+ # Its message collects all the individual errors that can be raised for
44
+ # each setting.
45
+ InvalidSettingsError = Class.new(StandardError) do
46
+ def initialize(errors)
47
+ @errors = errors
48
+ end
49
+
50
+ def to_s
51
+ <<~STR.strip
52
+ Could not initialize settings. The following settings were invalid:
53
+
54
+ #{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
55
+ STR
56
+ end
57
+ end
58
+
59
+ # @api private
60
+ EMPTY_STORE = Dry::Core::Constants::EMPTY_HASH
61
+
62
+ include Dry::Configurable
63
+
64
+ # @api private
65
+ def initialize(store = EMPTY_STORE)
66
+ errors = config._settings.map(&:name).reduce({}) do |errs, name|
67
+ public_send("#{name}=", store.fetch(name) { Dry::Core::Constants::Undefined })
68
+ errs
69
+ rescue => e # rubocop:disable Style/RescueStandardError
70
+ errs.merge(name => e)
71
+ end
72
+
73
+ raise InvalidSettingsError, errors if errors.any?
74
+ end
75
+
76
+ private
77
+
78
+ def method_missing(name, *args, &block)
79
+ if config.respond_to?(name)
80
+ config.send(name, *args, &block)
81
+ else
82
+ super
83
+ end
84
+ end
85
+
86
+ def respond_to_missing?(name, _include_all = false)
87
+ config.respond_to?(name) || super
88
+ end
89
+ end
90
+ end
data/lib/hanami/setup.rb CHANGED
@@ -4,6 +4,8 @@ require "bundler/setup"
4
4
  require "hanami"
5
5
 
6
6
  begin
7
- require File.join(Dir.pwd, "config/application")
8
- rescue LoadError # rubocop:disable Lint/SuppressedException
7
+ app_require_path = File.join(Dir.pwd, "config/app")
8
+ require app_require_path
9
+ rescue LoadError => e
10
+ raise e unless e.path == app_require_path
9
11
  end
@@ -1,13 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hanami/router"
4
- require "hanami/application/routing/middleware/stack"
4
+ require_relative "routing/middleware/stack"
5
5
 
6
6
  module Hanami
7
- class Application
8
- # Hanami application router
7
+ class Slice
8
+ # Hanami app router
9
9
  # @since 2.0.0
10
10
  class Router < ::Hanami::Router
11
+ # @api private
12
+ attr_reader :middleware_stack
13
+
11
14
  # @since 2.0.0
12
15
  # @api private
13
16
  def initialize(routes:, middleware_stack: Routing::Middleware::Stack.new, **kwargs, &blk)
@@ -27,32 +30,34 @@ module Hanami
27
30
 
28
31
  # @since 2.0.0
29
32
  # @api private
30
- def use(middleware, *args, &blk)
31
- @middleware_stack.use(middleware, *args, &blk)
33
+ def use(...)
34
+ middleware_stack.use(...)
32
35
  end
33
36
 
34
37
  # @since 2.0.0
35
38
  # @api private
36
39
  def scope(*args, &blk)
37
- @middleware_stack.with(args.first) do
40
+ middleware_stack.with(args.first) do
38
41
  super
39
42
  end
40
43
  end
41
44
 
42
45
  # @since 2.0.0
43
- def slice(name, at:, &blk)
44
- path = prefixed_path(at)
45
- @resolver.register_slice_at_path(name, path)
46
+ def slice(slice_name, at:, &blk)
47
+ blk ||= @resolver.find_slice(slice_name).routes
46
48
 
47
- scope(path, &blk)
49
+ prev_resolver = @resolver
50
+ @resolver = @resolver.to_slice(slice_name)
51
+
52
+ scope(prefixed_path(at), &blk)
53
+ ensure
54
+ @resolver = prev_resolver
48
55
  end
49
56
 
50
57
  # @since 2.0.0
51
58
  # @api private
52
59
  def to_rack_app
53
- return self if @middleware_stack.empty?
54
-
55
- @middleware_stack.to_rack_app(self)
60
+ middleware_stack.to_rack_app(self)
56
61
  end
57
62
  end
58
63
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Slice
5
+ # Hanami app routes helpers
6
+ #
7
+ # An instance of this class will be registered with slice (at the "routes" key). You
8
+ # can use it to access the route helpers for your app.
9
+ #
10
+ # @example
11
+ # MyApp::App["routes"].path(:root) # => "/"
12
+ #
13
+ # @see Hanami::Router::UrlHelpers
14
+ # @since 2.0.0
15
+ class RoutesHelper
16
+ # @since 2.0.0
17
+ # @api private
18
+ def initialize(router)
19
+ @router = router
20
+ end
21
+
22
+ # @see Hanami::Router::UrlHelpers#path
23
+ def path(...)
24
+ router.path(...)
25
+ end
26
+
27
+ # @see Hanami::Router::UrlHelpers#url
28
+ def url(...)
29
+ router.url(...)
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :router
35
+ end
36
+ end
37
+ end
@@ -3,7 +3,7 @@
3
3
  require "rack/builder"
4
4
 
5
5
  module Hanami
6
- class Application
6
+ class Slice
7
7
  module Routing
8
8
  # Hanami::Applicatione::Router middleware stack
9
9
  #
@@ -20,6 +20,10 @@ module Hanami
20
20
  ROOT_PREFIX = "/"
21
21
  private_constant :ROOT_PREFIX
22
22
 
23
+ # @since 2.0.0
24
+ # @api private
25
+ attr_reader :stack
26
+
23
27
  # @since 2.0.0
24
28
  # @api private
25
29
  def initialize
@@ -29,8 +33,35 @@ module Hanami
29
33
 
30
34
  # @since 2.0.0
31
35
  # @api private
32
- def use(middleware, *args, &blk)
33
- @stack[@prefix].push([middleware, args, blk])
36
+ def initialize_copy(source)
37
+ super
38
+ @prefix = source.instance_variable_get(:@prefix).dup
39
+ @stack = stack.dup
40
+ end
41
+
42
+ # @since 2.0.0
43
+ # @api private
44
+ def use(middleware, *args, before: nil, after: nil, &blk)
45
+ item = [middleware, args, blk]
46
+
47
+ if before
48
+ @stack[@prefix].insert((idx = index_of(before)).zero? ? 0 : idx - 1, item)
49
+ elsif after
50
+ @stack[@prefix].insert(index_of(after) + 1, item)
51
+ else
52
+ @stack[@prefix].push([middleware, args, blk])
53
+ end
54
+
55
+ self
56
+ end
57
+
58
+ # @since 2.0.0
59
+ # @api private
60
+ def update(other)
61
+ other.stack.each do |prefix, items|
62
+ stack[prefix].concat(items)
63
+ end
64
+ self
34
65
  end
35
66
 
36
67
  # @since 2.0.0
@@ -45,7 +76,7 @@ module Hanami
45
76
 
46
77
  # @since 2.0.0
47
78
  # @api private
48
- def to_rack_app(app) # rubocop:disable Metrics/MethodLength
79
+ def to_rack_app(app)
49
80
  s = self
50
81
 
51
82
  Rack::Builder.new do
@@ -58,7 +89,7 @@ module Hanami
58
89
 
59
90
  run app
60
91
  end
61
- end.to_app
92
+ end
62
93
  end
63
94
 
64
95
  # @since 2.0.0
@@ -82,6 +113,13 @@ module Hanami
82
113
  builder.map(prefix, &blk)
83
114
  end
84
115
  end
116
+
117
+ private
118
+
119
+ # @since 2.0.0
120
+ def index_of(middleware)
121
+ @stack[@prefix].index { |(m, *)| m.equal?(middleware) }
122
+ end
85
123
  end
86
124
  end
87
125
  end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Slice
5
+ module Routing
6
+ # @since 2.0.0
7
+ class UnknownActionError < Hanami::Error
8
+ def initialize(identifier)
9
+ super("unknown action referenced in router: `#{identifier.inspect}'")
10
+ end
11
+ end
12
+
13
+ # @since 2.0.0
14
+ class NotCallableEndpointError < StandardError
15
+ def initialize(endpoint)
16
+ super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
17
+ end
18
+ end
19
+
20
+ # Hanami app router endpoint resolver
21
+ #
22
+ # @since 2.0.0
23
+ class Resolver
24
+ SLICE_ACTIONS_KEY_NAMESPACE = "actions"
25
+
26
+ # @api private
27
+ # @since 2.0.0
28
+ def initialize(slice:)
29
+ @slice = slice
30
+ end
31
+
32
+ # @api private
33
+ # @since 2.0.0
34
+ def find_slice(slice_name)
35
+ slice.slices[slice_name]
36
+ end
37
+
38
+ # @api private
39
+ # @since 2.0.0
40
+ def to_slice(slice_name)
41
+ self.class.new(slice: find_slice(slice_name))
42
+ end
43
+
44
+ # @api private
45
+ # @since 2.0.0
46
+ def call(_path, endpoint)
47
+ endpoint =
48
+ case endpoint
49
+ when String
50
+ resolve_slice_action(endpoint)
51
+ when Class
52
+ endpoint.respond_to?(:call) ? endpoint : endpoint.new
53
+ else
54
+ endpoint
55
+ end
56
+
57
+ unless endpoint.respond_to?(:call)
58
+ raise NotCallableEndpointError.new(endpoint)
59
+ end
60
+
61
+ endpoint
62
+ end
63
+
64
+ private
65
+
66
+ # @api private
67
+ # @since 2.0.0
68
+ attr_reader :slice
69
+
70
+ # @api private
71
+ # @since 2.0.0
72
+ def resolve_slice_action(key)
73
+ action_key = "#{SLICE_ACTIONS_KEY_NAMESPACE}.#{key}"
74
+
75
+ ensure_action_in_slice(action_key)
76
+
77
+ # Lazily resolve action from the slice to reduce router initialization time, and
78
+ # circumvent endless loops from the action requiring access to router-related
79
+ # concerns (which may not be fully loaded at the time of reading the routes)
80
+ -> (*args) {
81
+ action = slice.resolve(action_key) do
82
+ raise UnknownActionError.new(key)
83
+ end
84
+
85
+ action.call(*args)
86
+ }
87
+ end
88
+
89
+ def ensure_action_in_slice(key)
90
+ return unless slice.booted?
91
+
92
+ raise UnknownActionError.new(key) unless slice.key?(key)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../constants"
4
+
5
+ module Hanami
6
+ class Slice
7
+ # Infers a view name for automatically rendering within actions.
8
+ #
9
+ # @api private
10
+ # @since 2.0.0
11
+ class ViewNameInferrer
12
+ ALTERNATIVE_NAMES = {
13
+ "create" => "new",
14
+ "update" => "edit"
15
+ }.freeze
16
+
17
+ class << self
18
+ # Returns an array of container keys for views matching the given action.
19
+ #
20
+ # Also provides alternative view keys for common RESTful actions.
21
+ #
22
+ # @example
23
+ # ViewNameInferrer.call(action_name: "Main::Actions::Posts::Create", slice: Main::Slice)
24
+ # # => ["views.posts.create", "views.posts.new"]
25
+ #
26
+ # @param action_name [String] action class name
27
+ # @param slice [Hanami::Slice, Hanami::Application] Hanami slice containing the action
28
+ #
29
+ # @return [Array<string>] array of paired view container keys
30
+ def call(action_class_name:, slice:)
31
+ action_key_base = slice.config.actions.name_inference_base
32
+ view_key_base = slice.config.actions.view_name_inference_base
33
+
34
+ action_name_key = action_name_key(action_class_name, slice, action_key_base)
35
+
36
+ view_key = [view_key_base, action_name_key].compact.join(CONTAINER_KEY_DELIMITER)
37
+
38
+ [view_key, alternative_view_key(view_key)].compact
39
+ end
40
+
41
+ private
42
+
43
+ def action_name_key(action_name, slice, key_base)
44
+ slice
45
+ .inflector
46
+ .underscore(action_name)
47
+ .sub(%r{^#{slice.slice_name.path}#{PATH_DELIMITER}}, "")
48
+ .sub(%r{^#{key_base}#{PATH_DELIMITER}}, "")
49
+ .gsub("/", CONTAINER_KEY_DELIMITER)
50
+ end
51
+
52
+ def alternative_view_key(view_key)
53
+ parts = view_key.split(CONTAINER_KEY_DELIMITER)
54
+
55
+ alternative_name = ALTERNATIVE_NAMES[parts.last]
56
+ return unless alternative_name
57
+
58
+ [parts[0..-2], alternative_name].join(CONTAINER_KEY_DELIMITER)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end