hanami 2.0.3 → 2.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/LICENSE.md +1 -1
- data/README.md +25 -9
- data/hanami.gemspec +2 -2
- data/lib/hanami/config/actions.rb +0 -4
- data/lib/hanami/config/views.rb +0 -4
- data/lib/hanami/config.rb +54 -0
- data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
- data/lib/hanami/extensions/action.rb +4 -4
- data/lib/hanami/extensions/router/errors.rb +58 -0
- data/lib/hanami/extensions/view/context.rb +129 -60
- data/lib/hanami/extensions/view/part.rb +26 -0
- data/lib/hanami/extensions/view/scope.rb +26 -0
- data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
- data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
- data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
- data/lib/hanami/extensions/view/standard_helpers.rb +14 -0
- data/lib/hanami/extensions.rb +10 -3
- data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
- data/lib/hanami/helpers/form_helper/values.rb +75 -0
- data/lib/hanami/helpers/form_helper.rb +213 -0
- data/lib/hanami/middleware/public_errors_app.rb +75 -0
- data/lib/hanami/middleware/render_errors.rb +93 -0
- data/lib/hanami/slice.rb +27 -2
- data/lib/hanami/slice_configurable.rb +3 -2
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami/web/rack_logger.rb +1 -1
- data/lib/hanami.rb +1 -1
- data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
- data/spec/integration/action/view_rendering_spec.rb +0 -18
- data/spec/integration/rack_app/middleware_spec.rb +23 -23
- data/spec/integration/rack_app/rack_app_spec.rb +5 -1
- data/spec/integration/view/config/default_context_spec.rb +149 -0
- data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
- data/spec/integration/view/config/part_class_spec.rb +147 -0
- data/spec/integration/view/config/part_namespace_spec.rb +103 -0
- data/spec/integration/view/config/paths_spec.rb +119 -0
- data/spec/integration/view/config/scope_class_spec.rb +147 -0
- data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
- data/spec/integration/view/config/template_spec.rb +38 -0
- data/spec/integration/view/context/request_spec.rb +3 -7
- data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
- data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
- data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
- data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
- data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
- data/spec/integration/view/slice_configuration_spec.rb +9 -9
- data/spec/integration/web/render_detailed_errors_spec.rb +90 -0
- data/spec/integration/web/render_errors_spec.rb +240 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/matchers.rb +32 -0
- data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -4
- data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
- data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
- data/spec/unit/hanami/config/views_spec.rb +0 -18
- data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
- data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
- data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
- data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
- data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
- data/spec/unit/hanami/version_spec.rb +1 -1
- data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
- metadata +65 -33
- data/spec/integration/action/view_integration_spec.rb +0 -165
- data/spec/integration/view/part_namespace_spec.rb +0 -96
- data/spec/integration/view/path_spec.rb +0 -56
- data/spec/integration/view/template_spec.rb +0 -68
- data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
- data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
- data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
- data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
- data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
- data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
- data/spec/isolation/hanami/boot/success_spec.rb +0 -50
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Helpers
|
5
|
+
module FormHelper
|
6
|
+
# Values from params and form helpers.
|
7
|
+
#
|
8
|
+
# It's responsible to populate input values with data coming from params
|
9
|
+
# and inline values specified via form helpers like `text_field`.
|
10
|
+
#
|
11
|
+
# @since 2.0.0
|
12
|
+
# @api private
|
13
|
+
class Values
|
14
|
+
# @since 2.0.0
|
15
|
+
# @api private
|
16
|
+
GET_SEPARATOR = "."
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
# @since 2.0.0
|
20
|
+
attr_reader :csrf_token
|
21
|
+
|
22
|
+
# @since 2.0.0
|
23
|
+
# @api private
|
24
|
+
def initialize(values: {}, params: {}, csrf_token: nil)
|
25
|
+
@values = values.to_h
|
26
|
+
@params = params.to_h
|
27
|
+
@csrf_token = csrf_token
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the value (if present) for the given key.
|
31
|
+
# Nested values are expressed with an array if symbols.
|
32
|
+
#
|
33
|
+
# @since 2.0.0
|
34
|
+
# @api private
|
35
|
+
def get(*keys)
|
36
|
+
get_from_params(*keys) || get_from_values(*keys)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @since 2.0.0
|
42
|
+
# @api private
|
43
|
+
def get_from_params(*keys)
|
44
|
+
keys.map! { |key| /\A\d+\z/.match?(key.to_s) ? key.to_s.to_i : key }
|
45
|
+
@params.dig(*keys)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @since 2.0.0
|
49
|
+
# @api private
|
50
|
+
def get_from_values(*keys)
|
51
|
+
head, *tail = *keys
|
52
|
+
result = @values[head]
|
53
|
+
|
54
|
+
tail.each do |k|
|
55
|
+
break if result.nil?
|
56
|
+
|
57
|
+
result = dig(result, k)
|
58
|
+
end
|
59
|
+
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
# @since 2.0.0
|
64
|
+
# @api private
|
65
|
+
def dig(base, key)
|
66
|
+
case base
|
67
|
+
when ::Hash then base[key]
|
68
|
+
when Array then base[key.to_s.to_i]
|
69
|
+
when ->(r) { r.respond_to?(key) } then base.public_send(key)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/view"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module Helpers
|
7
|
+
# Helper methods for generating HTML forms.
|
8
|
+
#
|
9
|
+
# These helpers will be automatically available in your view templates, part classes and scope
|
10
|
+
# classes.
|
11
|
+
#
|
12
|
+
# This module provides one primary method: {#form_for}, yielding an HTML form builder. This
|
13
|
+
# integrates with request params and template locals to populate the form with appropriate
|
14
|
+
# values.
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
# @since 2.0.0
|
18
|
+
module FormHelper
|
19
|
+
require_relative "form_helper/form_builder"
|
20
|
+
|
21
|
+
# Default HTTP method for form
|
22
|
+
#
|
23
|
+
# @since 2.0.0
|
24
|
+
# @api private
|
25
|
+
DEFAULT_METHOD = "POST"
|
26
|
+
|
27
|
+
# Default charset
|
28
|
+
#
|
29
|
+
# @since 2.0.0
|
30
|
+
# @api private
|
31
|
+
DEFAULT_CHARSET = "utf-8"
|
32
|
+
|
33
|
+
# CSRF Token session key
|
34
|
+
#
|
35
|
+
# This name of this key is shared with the hanami and hanami-controller gems.
|
36
|
+
#
|
37
|
+
# @since 2.0.0
|
38
|
+
# @api private
|
39
|
+
CSRF_TOKEN = :_csrf_token
|
40
|
+
|
41
|
+
include Hanami::View::Helpers::TagHelper
|
42
|
+
|
43
|
+
# Yields a form builder for constructing an HTML form and returns the resulting form string.
|
44
|
+
#
|
45
|
+
# See {FormHelper::FormBuilder} for the methods for building the form's fields.
|
46
|
+
#
|
47
|
+
# @overload form_for(base_name, url, values: _form_for_values, params: _form_for_params, **attributes)
|
48
|
+
# Builds the form using the given base name for all fields.
|
49
|
+
#
|
50
|
+
# @param base_name [String] the base
|
51
|
+
# @param url [String] the URL for submitting the form
|
52
|
+
# @param values [Hash] values to be used for populating form field values; optional,
|
53
|
+
# defaults to the template's locals or to a part's `{name => self}`
|
54
|
+
# @param params [Hash] request param values to be used for populating form field values;
|
55
|
+
# these are used in preference over the `values`; optional, defaults to the current
|
56
|
+
# request's params
|
57
|
+
# @param attributes [Hash] the HTML attributes for the form tag
|
58
|
+
# @yieldparam [FormHelper::FormBuilder] f the form builder
|
59
|
+
#
|
60
|
+
# @overload form_for(url, values: _form_for_values, params: _form_for_params, **attributes)
|
61
|
+
# @param url [String] the URL for submitting the form
|
62
|
+
# @param values [Hash] values to be used for populating form field values; optional,
|
63
|
+
# defaults to the template's locals or to a part's `{name => self}`
|
64
|
+
# @param params [Hash] request param values to be used for populating form field values;
|
65
|
+
# these are used in preference over the `values`; optional, defaults to the current
|
66
|
+
# request's params
|
67
|
+
# @param attributes [Hash] the HTML attributes for the form tag
|
68
|
+
# @yieldparam [FormHelper::FormBuilder] f the form builder
|
69
|
+
#
|
70
|
+
# @return [String] the form HTML
|
71
|
+
#
|
72
|
+
# @see FormHelper
|
73
|
+
# @see FormHelper::FormBuilder
|
74
|
+
#
|
75
|
+
# @example Basic usage
|
76
|
+
# <%= form_for("book", "/books", class: "form-horizontal") do |f| %>
|
77
|
+
# <div>
|
78
|
+
# <%= f.label "title" %>
|
79
|
+
# <%= f.text_field "title", class: "form-control" %>
|
80
|
+
# </div>
|
81
|
+
#
|
82
|
+
# <%= f.submit "Create" %>
|
83
|
+
# <% end %>
|
84
|
+
#
|
85
|
+
# =>
|
86
|
+
# <form action="/books" method="POST" accept-charset="utf-8" class="form-horizontal">
|
87
|
+
# <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
|
88
|
+
# <div>
|
89
|
+
# <label for="book-title">Title</label>
|
90
|
+
# <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
|
91
|
+
# </div>
|
92
|
+
#
|
93
|
+
# <button type="submit">Create</button>
|
94
|
+
# </form>
|
95
|
+
#
|
96
|
+
# @example Without base name
|
97
|
+
#
|
98
|
+
# <%= form_for("/books", class: "form-horizontal") do |f| %>
|
99
|
+
# <div>
|
100
|
+
# <%= f.label "books.title" %>
|
101
|
+
# <%= f.text_field "books.title", class: "form-control" %>
|
102
|
+
# </div>
|
103
|
+
#
|
104
|
+
# <%= f.submit "Create" %>
|
105
|
+
# <% end %>
|
106
|
+
#
|
107
|
+
# =>
|
108
|
+
# <form action="/books" method="POST" accept-charset="utf-8" class="form-horizontal">
|
109
|
+
# <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
|
110
|
+
# <div>
|
111
|
+
# <label for="book-title">Title</label>
|
112
|
+
# <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
|
113
|
+
# </div>
|
114
|
+
#
|
115
|
+
# <button type="submit">Create</button>
|
116
|
+
# </form>
|
117
|
+
#
|
118
|
+
# @example Method override
|
119
|
+
# <%= form_for("/books/123", method: :put) do |f|
|
120
|
+
# <%= f.text_field "book.title" %>
|
121
|
+
# <%= f.submit "Update" %>
|
122
|
+
# <% end %>
|
123
|
+
#
|
124
|
+
# =>
|
125
|
+
# <form action="/books/123" accept-charset="utf-8" method="POST">
|
126
|
+
# <input type="hidden" name="_method" value="PUT">
|
127
|
+
# <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
|
128
|
+
# <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
|
129
|
+
#
|
130
|
+
# <button type="submit">Update</button>
|
131
|
+
# </form>
|
132
|
+
#
|
133
|
+
# @example Overriding values
|
134
|
+
# <%= form_for("/songs", values: {song: {title: "Envision"}}) do |f| %>
|
135
|
+
# <%= f.text_field "song.title" %>
|
136
|
+
# <%= f.submit "Create" %>
|
137
|
+
# <%= end %>
|
138
|
+
#
|
139
|
+
# =>
|
140
|
+
# <form action="/songs" accept-charset="utf-8" method="POST">
|
141
|
+
# <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
|
142
|
+
# <input type="text" name="song[title]" id="song-title" value="Envision">
|
143
|
+
#
|
144
|
+
# <button type="submit">Create</button>
|
145
|
+
# </form>
|
146
|
+
#
|
147
|
+
# @api public
|
148
|
+
# @since 2.0.0
|
149
|
+
def form_for(base_name, url = nil, values: _form_for_values, params: _form_for_params, **attributes)
|
150
|
+
url, base_name = base_name, nil if url.nil?
|
151
|
+
|
152
|
+
values = Values.new(values: values, params: params, csrf_token: _form_csrf_token)
|
153
|
+
|
154
|
+
builder = FormBuilder.new(
|
155
|
+
base_name: base_name,
|
156
|
+
values: values,
|
157
|
+
inflector: _context.inflector,
|
158
|
+
form_attributes: attributes
|
159
|
+
)
|
160
|
+
|
161
|
+
content = (block_given? ? yield(builder) : "").html_safe
|
162
|
+
|
163
|
+
builder.call(content, action: url, **attributes)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns CSRF meta tags for use via unobtrusive JavaScript (UJS) libraries.
|
167
|
+
#
|
168
|
+
# @return [String, nil] the tags, if a CSRF token is available, or nil
|
169
|
+
#
|
170
|
+
# @example
|
171
|
+
# csrf_meta_tags
|
172
|
+
#
|
173
|
+
# =>
|
174
|
+
# <meta name="csrf-param" content="_csrf_token">
|
175
|
+
# <meta name="csrf-token" content="4a038be85b7603c406dcbfad4b9cdf91ec6ca138ed6441163a07bb0fdfbe25b5">
|
176
|
+
#
|
177
|
+
# @api public
|
178
|
+
# @since 2.0.0
|
179
|
+
def csrf_meta_tags
|
180
|
+
return unless (token = _form_csrf_token)
|
181
|
+
|
182
|
+
tag.meta(name: "csrf-param", content: CSRF_TOKEN) +
|
183
|
+
tag.meta(name: "csrf-token", content: token)
|
184
|
+
end
|
185
|
+
|
186
|
+
# @api private
|
187
|
+
# @since 2.0.0
|
188
|
+
def _form_for_values
|
189
|
+
if respond_to?(:_locals) # Scope
|
190
|
+
_locals
|
191
|
+
elsif respond_to?(:_name) # Part
|
192
|
+
{_name => self}
|
193
|
+
else
|
194
|
+
{}
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# @api private
|
199
|
+
# @since 2.0.0
|
200
|
+
def _form_for_params
|
201
|
+
_context.request.params
|
202
|
+
end
|
203
|
+
|
204
|
+
# @since 2.0.0
|
205
|
+
# @api private
|
206
|
+
def _form_csrf_token
|
207
|
+
return unless _context.request.session_enabled?
|
208
|
+
|
209
|
+
_context.csrf_token
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module Middleware
|
7
|
+
# The errors app given to {Hanami::Middleware::RenderErrors}, which renders a error responses
|
8
|
+
# from HTML pages kept in `public/` or as simple JSON structures.
|
9
|
+
#
|
10
|
+
# @see Hanami::Middleware::RenderErrors
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
# @since 2.1.0
|
14
|
+
class PublicErrorsApp
|
15
|
+
# @api private
|
16
|
+
# @since 2.1.0
|
17
|
+
attr_reader :public_path
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
# @since 2.1.0
|
21
|
+
def initialize(public_path)
|
22
|
+
@public_path = public_path
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
# @since 2.1.0
|
27
|
+
def call(env)
|
28
|
+
request = Rack::Request.new(env)
|
29
|
+
status = request.path_info[1..].to_i
|
30
|
+
content_type = request.get_header("HTTP_ACCEPT")
|
31
|
+
|
32
|
+
default_body = {
|
33
|
+
status: status,
|
34
|
+
error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500])
|
35
|
+
}
|
36
|
+
|
37
|
+
render(status, content_type, default_body)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def render(status, content_type, default_body)
|
43
|
+
body, rendered_content_type = render_content(status, content_type, default_body)
|
44
|
+
|
45
|
+
[
|
46
|
+
status,
|
47
|
+
{
|
48
|
+
"Content-Type" => "#{rendered_content_type}; charset=utf-8",
|
49
|
+
"Content-Length" => body.bytesize.to_s
|
50
|
+
},
|
51
|
+
[body]
|
52
|
+
]
|
53
|
+
end
|
54
|
+
|
55
|
+
def render_content(status, content_type, default_body)
|
56
|
+
if content_type.to_s.start_with?("application/json")
|
57
|
+
require "json"
|
58
|
+
[JSON.generate(default_body), "application/json"]
|
59
|
+
else
|
60
|
+
[render_html_content(status, default_body), "text/html"]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def render_html_content(status, default_body)
|
65
|
+
path = "#{public_path}/#{status}.html"
|
66
|
+
|
67
|
+
if File.exist?(path)
|
68
|
+
File.read(path)
|
69
|
+
else
|
70
|
+
default_body[:error]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
|
5
|
+
# rubocop:disable Lint/RescueException
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
module Middleware
|
9
|
+
# Rack middleware that rescues errors raised by the app renders friendly error responses, via a
|
10
|
+
# given "errors app".
|
11
|
+
#
|
12
|
+
# By default, this is enabled only in production mode.
|
13
|
+
#
|
14
|
+
# @see Hanami::Config#render_errors
|
15
|
+
# @see Hanani::Middleware::PublicErrorsApp
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
# @since 2.1.0
|
19
|
+
class RenderErrors
|
20
|
+
# @api private
|
21
|
+
# @since 2.1.0
|
22
|
+
class RenderableException
|
23
|
+
attr_reader :exception
|
24
|
+
attr_reader :responses
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
# @since 2.1.0
|
28
|
+
def initialize(exception, responses:)
|
29
|
+
@exception = exception
|
30
|
+
@responses = responses
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
# @since 2.1.0
|
35
|
+
def rescue_response?
|
36
|
+
responses.key?(exception.class.name)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @api private
|
40
|
+
# @since 2.1.0
|
41
|
+
def status_code
|
42
|
+
Rack::Utils.status_code(responses[exception.class.name])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
# @since 2.1.0
|
48
|
+
def initialize(app, config, errors_app)
|
49
|
+
@app = app
|
50
|
+
@config = config
|
51
|
+
@errors_app = errors_app
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
# @since 2.1.0
|
56
|
+
def call(env)
|
57
|
+
@app.call(env)
|
58
|
+
rescue Exception => exception
|
59
|
+
request = Rack::Request.new(env)
|
60
|
+
|
61
|
+
if @config.render_errors
|
62
|
+
render_exception(request, exception)
|
63
|
+
else
|
64
|
+
raise exception
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def render_exception(request, exception)
|
71
|
+
renderable = RenderableException.new(exception, responses: @config.render_error_responses)
|
72
|
+
|
73
|
+
status = renderable.status_code
|
74
|
+
request.path_info = "/#{status}"
|
75
|
+
request.set_header(Rack::REQUEST_METHOD, "GET")
|
76
|
+
|
77
|
+
@errors_app.call(request.env)
|
78
|
+
rescue Exception => failsafe_error
|
79
|
+
# rubocop:disable Style/StderrPuts
|
80
|
+
$stderr.puts "Error during exception rendering: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
81
|
+
# rubocop:enable Style/StderrPuts
|
82
|
+
|
83
|
+
[
|
84
|
+
500,
|
85
|
+
{"Content-Type" => "text/plain; charset=utf-8"},
|
86
|
+
["Internal Server Error"]
|
87
|
+
]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# rubocop:enable Lint/RescueException
|
data/lib/hanami/slice.rb
CHANGED
@@ -948,6 +948,7 @@ module Hanami
|
|
948
948
|
|
949
949
|
require_relative "slice/router"
|
950
950
|
|
951
|
+
slice = self
|
951
952
|
config = self.config
|
952
953
|
rack_monitor = self["rack.monitor"]
|
953
954
|
|
@@ -955,17 +956,41 @@ module Hanami
|
|
955
956
|
inspector: inspector,
|
956
957
|
routes: routes,
|
957
958
|
resolver: config.router.resolver.new(slice: self),
|
959
|
+
not_allowed: ROUTER_NOT_ALLOWED_HANDLER,
|
960
|
+
not_found: ROUTER_NOT_FOUND_HANDLER,
|
958
961
|
**config.router.options
|
959
962
|
) do
|
960
963
|
use(rack_monitor)
|
961
|
-
|
962
|
-
|
964
|
+
|
965
|
+
use(
|
966
|
+
Hanami::Middleware::RenderErrors,
|
967
|
+
config,
|
968
|
+
Hanami::Middleware::PublicErrorsApp.new(slice.root.join("public"))
|
969
|
+
)
|
970
|
+
|
971
|
+
if config.render_detailed_errors && Hanami.bundled?("hanami-webconsole")
|
972
|
+
require "hanami/webconsole"
|
973
|
+
use(Hanami::Webconsole::Middleware)
|
974
|
+
end
|
975
|
+
|
976
|
+
if Hanami.bundled?("hanami-controller") && config.actions.sessions.enabled?
|
977
|
+
use(*config.actions.sessions.middleware)
|
963
978
|
end
|
964
979
|
|
965
980
|
middleware_stack.update(config.middleware_stack)
|
966
981
|
end
|
967
982
|
end
|
968
983
|
|
984
|
+
ROUTER_NOT_ALLOWED_HANDLER = -> env, allowed_http_methods {
|
985
|
+
raise Hanami::Router::NotAllowedError.new(env, allowed_http_methods)
|
986
|
+
}.freeze
|
987
|
+
private_constant :ROUTER_NOT_ALLOWED_HANDLER
|
988
|
+
|
989
|
+
ROUTER_NOT_FOUND_HANDLER = -> env {
|
990
|
+
raise Hanami::Router::NotFoundError.new(env)
|
991
|
+
}.freeze
|
992
|
+
private_constant :ROUTER_NOT_FOUND_HANDLER
|
993
|
+
|
969
994
|
# rubocop:enable Metrics/AbcSize
|
970
995
|
end
|
971
996
|
# rubocop:enable Metrics/ModuleLength
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "constants"
|
3
4
|
require_relative "errors"
|
4
5
|
|
5
6
|
module Hanami
|
@@ -43,7 +44,7 @@ module Hanami
|
|
43
44
|
|
44
45
|
unless subclass.configured_for_slice?(slice)
|
45
46
|
subclass.configure_for_slice(slice)
|
46
|
-
subclass.configured_for_slices << slice
|
47
|
+
subclass.configured_for_slices << slice
|
47
48
|
end
|
48
49
|
end
|
49
50
|
end
|
@@ -58,7 +59,7 @@ module Hanami
|
|
58
59
|
|
59
60
|
slices = Hanami.app.slices.with_nested + [Hanami.app]
|
60
61
|
|
61
|
-
slices.detect { |slice| klass.name.
|
62
|
+
slices.detect { |slice| klass.name.start_with?("#{slice.namespace}#{MODULE_DELIMITER}") }
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
data/lib/hanami/version.rb
CHANGED
data/lib/hanami.rb
CHANGED
@@ -18,7 +18,7 @@ module Hanami
|
|
18
18
|
def self.loader
|
19
19
|
@loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
|
20
20
|
loader.ignore(
|
21
|
-
"#{loader.dirs.first}/hanami/{constants,boot,errors,prepare,rake_tasks,setup}.rb"
|
21
|
+
"#{loader.dirs.first}/hanami/{constants,boot,errors,extensions/router/errors,prepare,rake_tasks,setup}.rb"
|
22
22
|
)
|
23
23
|
end
|
24
24
|
end
|