hanami 2.0.2 → 2.1.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -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/logger.rb +1 -1
- 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 +28 -2
- data/lib/hanami/slice_configurable.rb +3 -2
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami/web/rack_logger.rb +8 -20
- 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/slices/slice_registrations_spec.rb +80 -0
- 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/logger_spec.rb +9 -0
- 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 +67 -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
@@ -843,6 +843,7 @@ module Hanami
|
|
843
843
|
container.config.name = slice_name.to_sym
|
844
844
|
container.config.root = root
|
845
845
|
container.config.provider_dirs = [File.join("config", "providers")]
|
846
|
+
container.config.registrations_dir = File.join("config", "registrations")
|
846
847
|
|
847
848
|
container.config.env = config.env
|
848
849
|
container.config.inflector = config.inflector
|
@@ -947,6 +948,7 @@ module Hanami
|
|
947
948
|
|
948
949
|
require_relative "slice/router"
|
949
950
|
|
951
|
+
slice = self
|
950
952
|
config = self.config
|
951
953
|
rack_monitor = self["rack.monitor"]
|
952
954
|
|
@@ -954,17 +956,41 @@ module Hanami
|
|
954
956
|
inspector: inspector,
|
955
957
|
routes: routes,
|
956
958
|
resolver: config.router.resolver.new(slice: self),
|
959
|
+
not_allowed: ROUTER_NOT_ALLOWED_HANDLER,
|
960
|
+
not_found: ROUTER_NOT_FOUND_HANDLER,
|
957
961
|
**config.router.options
|
958
962
|
) do
|
959
963
|
use(rack_monitor)
|
960
|
-
|
961
|
-
|
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)
|
962
978
|
end
|
963
979
|
|
964
980
|
middleware_stack.update(config.middleware_stack)
|
965
981
|
end
|
966
982
|
end
|
967
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
|
+
|
968
994
|
# rubocop:enable Metrics/AbcSize
|
969
995
|
end
|
970
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
@@ -29,7 +29,7 @@ module Hanami
|
|
29
29
|
ROUTER_PARAMS = "router.params"
|
30
30
|
private_constant :ROUTER_PARAMS
|
31
31
|
|
32
|
-
CONTENT_LENGTH = "
|
32
|
+
CONTENT_LENGTH = "CONTENT_LENGTH"
|
33
33
|
private_constant :CONTENT_LENGTH
|
34
34
|
|
35
35
|
MILISECOND = "ms"
|
@@ -38,30 +38,16 @@ module Hanami
|
|
38
38
|
MICROSECOND = "µs"
|
39
39
|
private_constant :MICROSECOND
|
40
40
|
|
41
|
-
# Dynamic extension used in
|
42
|
-
# @api private
|
43
|
-
module Production
|
44
|
-
private
|
45
|
-
|
46
|
-
# @since 1.0.0
|
47
|
-
# @api private
|
48
|
-
def data(env, status:, elapsed:)
|
49
|
-
payload = super
|
50
|
-
payload[:elapsed] = elapsed
|
51
|
-
payload[:elapsed_unit] = MICROSECOND
|
52
|
-
payload
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Dynamic extension used in non-production environments
|
41
|
+
# Dynamic extension used in development and test environments
|
57
42
|
# @api private
|
58
43
|
module Development
|
59
44
|
private
|
60
45
|
|
61
|
-
# @since
|
46
|
+
# @since 2.0.0
|
62
47
|
# @api private
|
63
48
|
def data(env, status:, elapsed:)
|
64
49
|
payload = super
|
50
|
+
payload.delete(:elapsed_unit)
|
65
51
|
payload[:elapsed] = elapsed > 1000 ? "#{elapsed / 1000}ms" : "#{elapsed}#{MICROSECOND}"
|
66
52
|
payload
|
67
53
|
end
|
@@ -71,7 +57,7 @@ module Hanami
|
|
71
57
|
# @since 2.0.0
|
72
58
|
def initialize(logger, env: :development)
|
73
59
|
@logger = logger
|
74
|
-
extend(
|
60
|
+
extend(Development) if %i[development test].include?(env)
|
75
61
|
end
|
76
62
|
|
77
63
|
# @api private
|
@@ -116,7 +102,9 @@ module Hanami
|
|
116
102
|
ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
|
117
103
|
path: "#{env[SCRIPT_NAME]}#{env[PATH_INFO]}",
|
118
104
|
length: extract_content_length(env),
|
119
|
-
params: env.fetch(ROUTER_PARAMS, EMPTY_PARAMS)
|
105
|
+
params: env.fetch(ROUTER_PARAMS, EMPTY_PARAMS),
|
106
|
+
elapsed: elapsed,
|
107
|
+
elapsed_unit: MICROSECOND,
|
120
108
|
}
|
121
109
|
end
|
122
110
|
|
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
|