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,26 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hanami/router"
4
- require_relative "routing/middleware/stack"
5
4
 
6
5
  module Hanami
7
6
  class Slice
8
- # Hanami app router
7
+ # `Hanami::Router` subclass with enhancements for use within Hanami apps.
8
+ #
9
+ # This is loaded from Hanami apps and slices and made available as their
10
+ # {Hanami::Slice::ClassMethods#router router}.
11
+ #
12
+ # @api private
9
13
  # @since 2.0.0
10
14
  class Router < ::Hanami::Router
11
15
  # @api private
16
+ # @since 2.0.0
12
17
  attr_reader :middleware_stack
13
18
 
19
+ # @api private
14
20
  # @since 2.0.0
21
+ attr_reader :path_prefix
22
+
15
23
  # @api private
16
- def initialize(routes:, middleware_stack: Routing::Middleware::Stack.new, **kwargs, &blk)
24
+ # @since 2.0.0
25
+ def initialize(routes:, middleware_stack: Routing::Middleware::Stack.new, prefix: ::Hanami::Router::DEFAULT_PREFIX, **kwargs, &blk)
26
+ @path_prefix = Hanami::Router::Prefix.new(prefix)
17
27
  @middleware_stack = middleware_stack
18
28
  instance_eval(&blk)
19
29
  super(**kwargs, &routes)
20
30
  end
21
31
 
22
- # @since 2.0.0
23
32
  # @api private
33
+ # @since 2.0.0
24
34
  def freeze
25
35
  return self if frozen?
26
36
 
@@ -28,20 +38,32 @@ module Hanami
28
38
  super
29
39
  end
30
40
 
31
- # @since 2.0.0
32
41
  # @api private
33
- def use(...)
34
- middleware_stack.use(...)
35
- end
36
-
37
42
  # @since 2.0.0
38
- # @api private
39
- def scope(*args, &blk)
40
- middleware_stack.with(args.first) do
41
- super
42
- end
43
+ def use(*args, **kwargs, &blk)
44
+ middleware_stack.use(*args, **kwargs.merge(path_prefix: path_prefix.to_s), &blk)
43
45
  end
44
46
 
47
+ # Yields a block for routes to resolve their action components from the given slice.
48
+ #
49
+ # An optional URL prefix may be supplied with `at:`.
50
+ #
51
+ # @example
52
+ # # config/routes.rb
53
+ #
54
+ # module MyApp
55
+ # class Routes < Hanami::Routes
56
+ # slice :admin, at: "/admin" do
57
+ # # Will route to the "actions.posts.index" component in Admin::Slice
58
+ # get "posts", to: "posts.index"
59
+ # end
60
+ # end
61
+ # end
62
+ #
63
+ # @param slice_name [Symbol] the slice's name
64
+ # @param at [String, nil] optional URL prefix for the routes
65
+ #
66
+ # @api public
45
67
  # @since 2.0.0
46
68
  def slice(slice_name, at:, &blk)
47
69
  blk ||= @resolver.find_slice(slice_name).routes
@@ -49,13 +71,13 @@ module Hanami
49
71
  prev_resolver = @resolver
50
72
  @resolver = @resolver.to_slice(slice_name)
51
73
 
52
- scope(prefixed_path(at), &blk)
74
+ scope(at, &blk)
53
75
  ensure
54
76
  @resolver = prev_resolver
55
77
  end
56
78
 
57
- # @since 2.0.0
58
79
  # @api private
80
+ # @since 2.0.0
59
81
  def to_rack_app
60
82
  middleware_stack.to_rack_app(self)
61
83
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hanami/router"
3
4
  require "hanami/middleware"
5
+ require "hanami/middleware/app"
4
6
  require "hanami/errors"
5
7
 
6
8
  module Hanami
@@ -11,8 +13,8 @@ module Hanami
11
13
  module Middleware
12
14
  # Wraps a rack app with a middleware stack
13
15
  #
14
- # We use this class to add middlewares to the rack application generated
15
- # from {Hanami::Slice::Router}.
16
+ # We use this class to add middlewares to the rack application generated from
17
+ # {Hanami::Slice::Router}.
16
18
  #
17
19
  # ```
18
20
  # stack = Hanami::Slice::Routing::Middleware::Stack.new
@@ -28,28 +30,31 @@ module Hanami
28
30
  # end
29
31
  # ```
30
32
  #
33
+ # @see Hanami::Config#middleware
34
+ #
31
35
  # @since 2.0.0
32
36
  # @api private
33
37
  class Stack
34
38
  include Enumerable
35
39
 
36
- # @since 2.0.0
37
- # @api private
38
- ROOT_PREFIX = "/"
39
- private_constant :ROOT_PREFIX
40
-
41
40
  # @since 2.0.0
42
41
  # @api private
43
42
  attr_reader :stack
44
43
 
45
- # @since 2.0.0
44
+ # Returns an array of Ruby namespaces from which to load middleware classes specified by
45
+ # symbol names given to {#use}.
46
+ #
47
+ # Defaults to `[Hanami::Middleware]`.
48
+ #
49
+ # @return [Array<Object>]
50
+ #
46
51
  # @api public
52
+ # @since 2.0.0
47
53
  attr_reader :namespaces
48
54
 
49
55
  # @since 2.0.0
50
56
  # @api private
51
57
  def initialize
52
- @prefix = ROOT_PREFIX
53
58
  @stack = Hash.new { |hash, key| hash[key] = [] }
54
59
  @namespaces = [Hanami::Middleware]
55
60
  end
@@ -58,23 +63,44 @@ module Hanami
58
63
  # @api private
59
64
  def initialize_copy(source)
60
65
  super
61
- @prefix = source.instance_variable_get(:@prefix).dup
62
66
  @stack = stack.dup
63
67
  @namespaces = namespaces.dup
64
68
  end
65
69
 
70
+ # Adds a middleware to the stack.
71
+ #
72
+ # @example
73
+ # # Using a symbol name; adds Hanami::Middleware::BodyParser.new([:json])
74
+ # middleware.use :body_parser, :json
75
+ #
76
+ # # Using a class name
77
+ # middleware.use MyMiddleware
78
+ #
79
+ # # Adding a middleware before or after others
80
+ # middleware.use MyMiddleware, before: SomeMiddleware
81
+ # middleware.use MyMiddleware, after: OtherMiddleware
82
+ #
83
+ # @param spec [Symbol, Class] the middleware name or class name
84
+ # @param args [Array, nil] Arguments to pass to the middleware's `.new` method
85
+ # @param before [Class, nil] an optional (already added) middleware class to add the
86
+ # middleware before
87
+ # @param after [Class, nil] an optional (already added) middleware class to add the
88
+ # middleware after
89
+ #
90
+ # @return [self]
91
+ #
92
+ # @api public
66
93
  # @since 2.0.0
67
- # @api private
68
- def use(spec, *args, before: nil, after: nil, &blk)
94
+ def use(spec, *args, path_prefix: ::Hanami::Router::DEFAULT_PREFIX, before: nil, after: nil, &blk)
69
95
  middleware = resolve_middleware_class(spec)
70
96
  item = [middleware, args, blk]
71
97
 
72
98
  if before
73
- @stack[@prefix].insert((idx = index_of(before)).zero? ? 0 : idx - 1, item)
99
+ @stack[path_prefix].insert((idx = index_of(before, path_prefix)).zero? ? 0 : idx - 1, item)
74
100
  elsif after
75
- @stack[@prefix].insert(index_of(after) + 1, item)
101
+ @stack[path_prefix].insert(index_of(after, path_prefix) + 1, item)
76
102
  else
77
- @stack[@prefix].push([middleware, args, blk])
103
+ @stack[path_prefix].push([middleware, args, blk])
78
104
  end
79
105
 
80
106
  self
@@ -83,22 +109,12 @@ module Hanami
83
109
  # @since 2.0.0
84
110
  # @api private
85
111
  def update(other)
86
- other.stack.each do |prefix, items|
87
- stack[prefix].concat(items)
112
+ other.stack.each do |path_prefix, items|
113
+ stack[path_prefix].concat(items)
88
114
  end
89
115
  self
90
116
  end
91
117
 
92
- # @since 2.0.0
93
- # @api private
94
- def with(path)
95
- prefix = @prefix
96
- @prefix = path
97
- yield
98
- ensure
99
- @prefix = prefix
100
- end
101
-
102
118
  # @since 2.0.0
103
119
  # @api private
104
120
  def to_rack_app(app)
@@ -106,20 +122,17 @@ module Hanami
106
122
  raise "Add \"rack\" to your `Gemfile` to run Hanami as a rack app"
107
123
  end
108
124
 
109
- require "rack/builder"
110
-
111
- s = self
125
+ mapping = to_hash
126
+ return app if mapping.empty?
112
127
 
113
- Rack::Builder.new do
114
- s.each do |prefix, stack|
115
- s.mapped(self, prefix) do
116
- stack.each do |middleware, args, blk|
117
- use(middleware, *args, &blk)
118
- end
119
- end
128
+ Hanami::Middleware::App.new(app, mapping)
129
+ end
120
130
 
121
- run app
122
- end
131
+ # @since 2.0.0
132
+ # @api private
133
+ def to_hash
134
+ @stack.each_with_object({}) do |(path, _), result|
135
+ result[path] = stack_for(path)
123
136
  end
124
137
  end
125
138
 
@@ -138,7 +151,7 @@ module Hanami
138
151
  # @since 2.0.0
139
152
  # @api private
140
153
  def mapped(builder, prefix, &blk)
141
- if prefix == ROOT_PREFIX
154
+ if prefix == ::Hanami::Router::DEFAULT_PREFIX
142
155
  builder.instance_eval(&blk)
143
156
  else
144
157
  builder.map(prefix, &blk)
@@ -148,8 +161,18 @@ module Hanami
148
161
  private
149
162
 
150
163
  # @since 2.0.0
151
- def index_of(middleware)
152
- @stack[@prefix].index { |(m, *)| m.equal?(middleware) }
164
+ def index_of(middleware, path_prefix)
165
+ @stack[path_prefix].index { |(m, *)| m.equal?(middleware) }
166
+ end
167
+
168
+ # @since 2.0.0
169
+ # @api private
170
+ def stack_for(current_path)
171
+ @stack.each_with_object([]) do |(path, stack), result|
172
+ next unless current_path.start_with?(path)
173
+
174
+ result.push(stack)
175
+ end.flatten(1)
153
176
  end
154
177
 
155
178
  # @since 2.0.0
@@ -173,6 +196,7 @@ module Hanami
173
196
  rescue LoadError # rubocop:disable Lint/SuppressedException
174
197
  end
175
198
 
199
+ # FIXME: Classify must use App inflector
176
200
  class_name = Hanami::Utils::String.classify(spec.to_s)
177
201
  namespace = namespaces.detect { |ns| ns.const_defined?(class_name) }
178
202
 
@@ -1,24 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../routes"
4
+
3
5
  module Hanami
4
6
  class Slice
7
+ # @api private
5
8
  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
9
  # Hanami app router endpoint resolver
21
10
  #
11
+ # This resolves endpoints objects from a slice container using the strings passed to `to:` as
12
+ # their container keys.
13
+ #
14
+ # @api private
22
15
  # @since 2.0.0
23
16
  class Resolver
24
17
  SLICE_ACTIONS_KEY_NAMESPACE = "actions"
@@ -55,7 +48,7 @@ module Hanami
55
48
  end
56
49
 
57
50
  unless endpoint.respond_to?(:call)
58
- raise NotCallableEndpointError.new(endpoint)
51
+ raise Routes::NotCallableEndpointError.new(endpoint)
59
52
  end
60
53
 
61
54
  endpoint
@@ -79,7 +72,7 @@ module Hanami
79
72
  # concerns (which may not be fully loaded at the time of reading the routes)
80
73
  -> (*args) {
81
74
  action = slice.resolve(action_key) do
82
- raise UnknownActionError.new(key)
75
+ raise Routes::MissingActionError.new(action_key, slice)
83
76
  end
84
77
 
85
78
  action.call(*args)
@@ -89,7 +82,7 @@ module Hanami
89
82
  def ensure_action_in_slice(key)
90
83
  return unless slice.booted?
91
84
 
92
- raise UnknownActionError.new(key) unless slice.key?(key)
85
+ raise Routes::MissingActionError.new(key, slice) unless slice.key?(key)
93
86
  end
94
87
  end
95
88
  end
@@ -23,7 +23,7 @@ module Hanami
23
23
  # ViewNameInferrer.call(action_name: "Main::Actions::Posts::Create", slice: Main::Slice)
24
24
  # # => ["views.posts.create", "views.posts.new"]
25
25
  #
26
- # @param action_name [String] action class name
26
+ # @param action_class_name [String] action class name
27
27
  # @param slice [Hanami::Slice, Hanami::Application] Hanami slice containing the action
28
28
  #
29
29
  # @return [Array<string>] array of paired view container keys