hanami 2.2.1 → 2.3.0.beta2
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 +49 -1
- data/README.md +20 -35
- data/hanami.gemspec +9 -8
- data/lib/hanami/app.rb +2 -0
- data/lib/hanami/config/actions/content_security_policy.rb +23 -0
- data/lib/hanami/config/actions.rb +21 -0
- data/lib/hanami/config/console.rb +79 -0
- data/lib/hanami/config/logger.rb +1 -1
- data/lib/hanami/config.rb +14 -1
- data/lib/hanami/constants.rb +3 -0
- data/lib/hanami/extensions/db/repo.rb +11 -6
- data/lib/hanami/extensions/view/context.rb +10 -10
- data/lib/hanami/extensions/view/slice_configured_context.rb +0 -7
- data/lib/hanami/helpers/assets_helper.rb +92 -25
- data/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
- data/lib/hanami/routes.rb +3 -3
- data/lib/hanami/slice.rb +34 -6
- data/lib/hanami/slice_registrar.rb +1 -1
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami.rb +10 -2
- data/spec/integration/action/format_config_spec.rb +6 -3
- data/spec/integration/action/slice_configuration_spec.rb +36 -36
- data/spec/integration/assets/cross_slice_assets_helpers_spec.rb +0 -1
- data/spec/integration/assets/serve_static_assets_spec.rb +1 -1
- data/spec/integration/container/autoloader_spec.rb +2 -0
- data/spec/integration/db/db_spec.rb +1 -1
- data/spec/integration/db/logging_spec.rb +63 -0
- data/spec/integration/db/repo_spec.rb +87 -2
- data/spec/integration/logging/exception_logging_spec.rb +6 -1
- data/spec/integration/rack_app/body_parser_spec.rb +2 -1
- data/spec/integration/rack_app/middleware_spec.rb +4 -11
- data/spec/integration/rack_app/rack_app_spec.rb +2 -2
- data/spec/integration/view/helpers/form_helper_spec.rb +1 -1
- data/spec/integration/web/content_security_policy_nonce_spec.rb +251 -0
- data/spec/support/app_integration.rb +2 -1
- data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +7 -0
- data/spec/unit/hanami/config/actions_spec.rb +2 -2
- data/spec/unit/hanami/config/console_spec.rb +22 -0
- data/spec/unit/hanami/env_spec.rb +10 -13
- data/spec/unit/hanami/slice_spec.rb +18 -0
- data/spec/unit/hanami/web/rack_logger_spec.rb +11 -4
- metadata +34 -29
- data/spec/integration/view/context/settings_spec.rb +0 -46
- data/spec/support/shared_examples/cli/generate/app.rb +0 -494
- data/spec/support/shared_examples/cli/generate/migration.rb +0 -32
- data/spec/support/shared_examples/cli/generate/model.rb +0 -81
- data/spec/support/shared_examples/cli/new.rb +0 -97
- data/spec/unit/hanami/version_spec.rb +0 -7
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "uri"
|
|
4
4
|
require "hanami/view"
|
|
5
|
+
require_relative "../constants"
|
|
5
6
|
|
|
6
7
|
# rubocop:disable Metrics/ModuleLength
|
|
7
8
|
|
|
@@ -61,7 +62,10 @@ module Hanami
|
|
|
61
62
|
|
|
62
63
|
# @since 0.3.0
|
|
63
64
|
# @api private
|
|
64
|
-
|
|
65
|
+
# TODO: we can drop the defined?-check and fallback once Ruby 3.3 becomes our minimum required version
|
|
66
|
+
ABSOLUTE_URL_MATCHER = (
|
|
67
|
+
defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
|
|
68
|
+
).make_regexp
|
|
65
69
|
|
|
66
70
|
# @since 1.1.0
|
|
67
71
|
# @api private
|
|
@@ -85,7 +89,12 @@ module Hanami
|
|
|
85
89
|
# name of the algorithm, then a hyphen, then the hash value of the file.
|
|
86
90
|
# If more than one algorithm is used, they"ll be separated by a space.
|
|
87
91
|
#
|
|
88
|
-
#
|
|
92
|
+
# If the Content Security Policy uses 'nonce' and the source is not
|
|
93
|
+
# absolute, the nonce value of the current request is automatically added
|
|
94
|
+
# as an attribute. You can override this with the `nonce: false` option.
|
|
95
|
+
# See {#content_security_policy_nonce} for more.
|
|
96
|
+
#
|
|
97
|
+
# @param sources [Array<String, #url>] one or more assets by name or absolute URL
|
|
89
98
|
#
|
|
90
99
|
# @return [Hanami::View::HTML::SafeString] the markup
|
|
91
100
|
#
|
|
@@ -136,6 +145,10 @@ module Hanami
|
|
|
136
145
|
#
|
|
137
146
|
# # <script src="/assets/application.js" type="text/javascript" defer="defer"></script>
|
|
138
147
|
#
|
|
148
|
+
# @example Disable nonce
|
|
149
|
+
#
|
|
150
|
+
# <%= javascript_tag "application", nonce: false %>
|
|
151
|
+
#
|
|
139
152
|
# @example Absolute URL
|
|
140
153
|
#
|
|
141
154
|
# <%= javascript_tag "https://code.jquery.com/jquery-2.1.4.min.js" %>
|
|
@@ -154,13 +167,15 @@ module Hanami
|
|
|
154
167
|
#
|
|
155
168
|
# # <script src="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
|
|
156
169
|
# # type="text/javascript"></script>
|
|
157
|
-
def javascript_tag(*
|
|
170
|
+
def javascript_tag(*sources, **options)
|
|
158
171
|
options = options.reject { |k, _| k.to_sym == :src }
|
|
172
|
+
nonce_option = options.delete(:nonce)
|
|
159
173
|
|
|
160
|
-
_safe_tags(*
|
|
174
|
+
_safe_tags(*sources) do |source|
|
|
161
175
|
attributes = {
|
|
162
|
-
src:
|
|
163
|
-
type: JAVASCRIPT_MIME_TYPE
|
|
176
|
+
src: _typed_url(source, JAVASCRIPT_EXT),
|
|
177
|
+
type: JAVASCRIPT_MIME_TYPE,
|
|
178
|
+
nonce: _nonce(source, nonce_option)
|
|
164
179
|
}
|
|
165
180
|
attributes.merge!(options)
|
|
166
181
|
|
|
@@ -189,7 +204,12 @@ module Hanami
|
|
|
189
204
|
# name of the algorithm, then a hyphen, then the hashed value of the file.
|
|
190
205
|
# If more than one algorithm is used, they"ll be separated by a space.
|
|
191
206
|
#
|
|
192
|
-
#
|
|
207
|
+
# If the Content Security Policy uses 'nonce' and the source is not
|
|
208
|
+
# absolute, the nonce value of the current request is automatically added
|
|
209
|
+
# as an attribute. You can override this with the `nonce: false` option.
|
|
210
|
+
# See {#content_security_policy_nonce} for more.
|
|
211
|
+
#
|
|
212
|
+
# @param sources [Array<String, #url>] one or more assets by name or absolute URL
|
|
193
213
|
#
|
|
194
214
|
# @return [Hanami::View::HTML::SafeString] the markup
|
|
195
215
|
#
|
|
@@ -214,6 +234,10 @@ module Hanami
|
|
|
214
234
|
# # <link href="/assets/application.css" type="text/css" rel="stylesheet">
|
|
215
235
|
# # <link href="/assets/dashboard.css" type="text/css" rel="stylesheet">
|
|
216
236
|
#
|
|
237
|
+
# @example Disable nonce
|
|
238
|
+
#
|
|
239
|
+
# <%= stylesheet_tag "application", nonce: false %>
|
|
240
|
+
#
|
|
217
241
|
# @example Subresource Integrity
|
|
218
242
|
#
|
|
219
243
|
# <%= stylesheet_tag "application" %>
|
|
@@ -247,19 +271,21 @@ module Hanami
|
|
|
247
271
|
#
|
|
248
272
|
# # <link href="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css"
|
|
249
273
|
# # type="text/css" rel="stylesheet">
|
|
250
|
-
def stylesheet_tag(*
|
|
274
|
+
def stylesheet_tag(*sources, **options)
|
|
251
275
|
options = options.reject { |k, _| k.to_sym == :href }
|
|
276
|
+
nonce_option = options.delete(:nonce)
|
|
252
277
|
|
|
253
|
-
_safe_tags(*
|
|
278
|
+
_safe_tags(*sources) do |source|
|
|
254
279
|
attributes = {
|
|
255
|
-
href:
|
|
280
|
+
href: _typed_url(source, STYLESHEET_EXT),
|
|
256
281
|
type: STYLESHEET_MIME_TYPE,
|
|
257
|
-
rel: STYLESHEET_REL
|
|
282
|
+
rel: STYLESHEET_REL,
|
|
283
|
+
nonce: _nonce(source, nonce_option)
|
|
258
284
|
}
|
|
259
285
|
attributes.merge!(options)
|
|
260
286
|
|
|
261
287
|
if _context.assets.subresource_integrity? || attributes.include?(:integrity)
|
|
262
|
-
attributes[:integrity] ||= _subresource_integrity_value(
|
|
288
|
+
attributes[:integrity] ||= _subresource_integrity_value(source, STYLESHEET_EXT)
|
|
263
289
|
attributes[:crossorigin] ||= CROSSORIGIN_ANONYMOUS
|
|
264
290
|
end
|
|
265
291
|
|
|
@@ -626,7 +652,7 @@ module Hanami
|
|
|
626
652
|
#
|
|
627
653
|
# If CDN mode is on, it returns the absolute URL of the asset.
|
|
628
654
|
#
|
|
629
|
-
# @param
|
|
655
|
+
# @param source [String, #url] the asset name or asset object
|
|
630
656
|
#
|
|
631
657
|
# @return [String] the asset path
|
|
632
658
|
#
|
|
@@ -665,42 +691,71 @@ module Hanami
|
|
|
665
691
|
# <%= asset_url "application.js" %>
|
|
666
692
|
#
|
|
667
693
|
# # "https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
|
|
668
|
-
def asset_url(
|
|
669
|
-
return
|
|
670
|
-
return
|
|
694
|
+
def asset_url(source)
|
|
695
|
+
return source.url if source.respond_to?(:url)
|
|
696
|
+
return source if _absolute_url?(source)
|
|
671
697
|
|
|
672
|
-
_context.assets[
|
|
698
|
+
_context.assets[source].url
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
# Random per request nonce value for Content Security Policy (CSP) rules.
|
|
702
|
+
#
|
|
703
|
+
# If the `Hanami::Middleware::ContentSecurityPolicyNonce` middleware is
|
|
704
|
+
# in use, this helper returns the nonce value for the current request
|
|
705
|
+
# or `nil` otherwise.
|
|
706
|
+
#
|
|
707
|
+
# For this policy to work in the browser, you have to add the `'nonce'`
|
|
708
|
+
# placeholder to the script and/or style source policy rule. It will be
|
|
709
|
+
# substituted by the current nonce value like `'nonce-A12OggyZ'.
|
|
710
|
+
#
|
|
711
|
+
# @return [String, nil] nonce value of the current request
|
|
712
|
+
#
|
|
713
|
+
# @since x.x.x
|
|
714
|
+
#
|
|
715
|
+
# @example App configuration
|
|
716
|
+
#
|
|
717
|
+
# config.middleware.use Hanami::Middleware::ContentSecurityPolicyNonce
|
|
718
|
+
# config.actions.content_security_policy[:script_src] = "'self' 'nonce'"
|
|
719
|
+
# config.actions.content_security_policy[:style_src] = "'self' 'nonce'"
|
|
720
|
+
#
|
|
721
|
+
# @example View helper
|
|
722
|
+
#
|
|
723
|
+
# <script nonce="<%= content_security_policy_nonce %>">
|
|
724
|
+
def content_security_policy_nonce
|
|
725
|
+
return unless _context.request?
|
|
726
|
+
|
|
727
|
+
_context.request.env[CONTENT_SECURITY_POLICY_NONCE_REQUEST_KEY]
|
|
673
728
|
end
|
|
674
729
|
|
|
675
730
|
private
|
|
676
731
|
|
|
677
732
|
# @since 2.1.0
|
|
678
733
|
# @api private
|
|
679
|
-
def _safe_tags(*
|
|
734
|
+
def _safe_tags(*sources, &blk)
|
|
680
735
|
::Hanami::View::HTML::SafeString.new(
|
|
681
|
-
|
|
736
|
+
sources.map(&blk).join(NEW_LINE_SEPARATOR)
|
|
682
737
|
)
|
|
683
738
|
end
|
|
684
739
|
|
|
685
740
|
# @since 2.1.0
|
|
686
741
|
# @api private
|
|
687
|
-
def
|
|
742
|
+
def _typed_url(source, ext)
|
|
688
743
|
source = "#{source}#{ext}" if source.is_a?(String) && _append_extension?(source, ext)
|
|
689
744
|
asset_url(source)
|
|
690
745
|
end
|
|
691
746
|
|
|
692
747
|
# @api private
|
|
693
|
-
def _subresource_integrity_value(
|
|
694
|
-
return if _absolute_url?(
|
|
748
|
+
def _subresource_integrity_value(source, ext)
|
|
749
|
+
return if _absolute_url?(source)
|
|
695
750
|
|
|
696
|
-
|
|
697
|
-
_context.assets[
|
|
751
|
+
source = "#{source}#{ext}" unless /#{Regexp.escape(ext)}\z/.match?(source)
|
|
752
|
+
_context.assets[source].sri
|
|
698
753
|
end
|
|
699
754
|
|
|
700
755
|
# @since 2.1.0
|
|
701
756
|
# @api private
|
|
702
757
|
def _absolute_url?(source)
|
|
703
|
-
ABSOLUTE_URL_MATCHER.match(source)
|
|
758
|
+
ABSOLUTE_URL_MATCHER.match?(source.respond_to?(:url) ? source.url : source)
|
|
704
759
|
end
|
|
705
760
|
|
|
706
761
|
# @since 1.2.0
|
|
@@ -711,6 +766,18 @@ module Hanami
|
|
|
711
766
|
_context.assets.crossorigin?(source)
|
|
712
767
|
end
|
|
713
768
|
|
|
769
|
+
# @since x.x.x
|
|
770
|
+
# @api private
|
|
771
|
+
def _nonce(source, nonce_option)
|
|
772
|
+
if nonce_option == false
|
|
773
|
+
nil
|
|
774
|
+
elsif nonce_option == true || (nonce_option.nil? && !_absolute_url?(source))
|
|
775
|
+
content_security_policy_nonce
|
|
776
|
+
else
|
|
777
|
+
nonce_option
|
|
778
|
+
end
|
|
779
|
+
end
|
|
780
|
+
|
|
714
781
|
# @since 2.1.0
|
|
715
782
|
# @api private
|
|
716
783
|
def _source_options(src, options, &blk)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
require_relative "../constants"
|
|
6
|
+
|
|
7
|
+
module Hanami
|
|
8
|
+
module Middleware
|
|
9
|
+
# Generates a random per request nonce value like `mSMnSwfVZVe+LyQy1SPCGw==`, stores it as
|
|
10
|
+
# `"hanami.content_security_policy_nonce"` in the Rack environment, and replaces all occurrences
|
|
11
|
+
# of `'nonce'` in the `Content-Security-Policy header with the actual nonce value for the
|
|
12
|
+
# request, e.g. `'nonce-mSMnSwfVZVe+LyQy1SPCGw=='`.
|
|
13
|
+
#
|
|
14
|
+
# @see Hanami::Middleware::ContentSecurityPolicyNonce
|
|
15
|
+
#
|
|
16
|
+
# @api private
|
|
17
|
+
# @since x.x.x
|
|
18
|
+
class ContentSecurityPolicyNonce
|
|
19
|
+
# @api private
|
|
20
|
+
# @since x.x.x
|
|
21
|
+
def initialize(app)
|
|
22
|
+
@app = app
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
# @since x.x.x
|
|
27
|
+
def call(env)
|
|
28
|
+
return @app.call(env) unless Hanami.app.config.actions.content_security_policy?
|
|
29
|
+
|
|
30
|
+
args = nonce_generator.arity == 1 ? [Rack::Request.new(env)] : []
|
|
31
|
+
request_nonce = nonce_generator.call(*args)
|
|
32
|
+
|
|
33
|
+
env[CONTENT_SECURITY_POLICY_NONCE_REQUEST_KEY] = request_nonce
|
|
34
|
+
|
|
35
|
+
_, headers, _ = response = @app.call(env)
|
|
36
|
+
|
|
37
|
+
headers["Content-Security-Policy"] = sub_nonce(headers["Content-Security-Policy"], request_nonce)
|
|
38
|
+
|
|
39
|
+
response
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def nonce_generator
|
|
45
|
+
Hanami.app.config.actions.content_security_policy_nonce_generator
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def sub_nonce(string, nonce)
|
|
49
|
+
string&.gsub("'nonce'", "'nonce-#{nonce}'")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/hanami/routes.rb
CHANGED
|
@@ -36,14 +36,14 @@ module Hanami
|
|
|
36
36
|
def initialize(action_key, slice)
|
|
37
37
|
action_path = action_key.gsub(CONTAINER_KEY_DELIMITER, PATH_DELIMITER)
|
|
38
38
|
action_constant = slice.inflector.camelize(
|
|
39
|
-
|
|
39
|
+
slice.inflector.underscore(slice.namespace.to_s) + PATH_DELIMITER + action_path
|
|
40
40
|
)
|
|
41
|
-
|
|
41
|
+
action_file_path = slice.relative_source_path.join(action_path).to_s.concat(RB_EXT)
|
|
42
42
|
|
|
43
43
|
super(<<~MSG)
|
|
44
44
|
Could not find action with key #{action_key.inspect} in #{slice}
|
|
45
45
|
|
|
46
|
-
To fix this, define the action class #{action_constant} in #{
|
|
46
|
+
To fix this, define the action class #{action_constant} in #{action_file_path}
|
|
47
47
|
MSG
|
|
48
48
|
end
|
|
49
49
|
end
|
data/lib/hanami/slice.rb
CHANGED
|
@@ -223,6 +223,18 @@ module Hanami
|
|
|
223
223
|
app? ? root.join(APP_DIR) : root
|
|
224
224
|
end
|
|
225
225
|
|
|
226
|
+
# Returns the slice's root component directory, as a path relative to the app's root.
|
|
227
|
+
#
|
|
228
|
+
# @return [Pathname]
|
|
229
|
+
#
|
|
230
|
+
# @see #source_path
|
|
231
|
+
#
|
|
232
|
+
# @api public
|
|
233
|
+
# @since 2.3.0
|
|
234
|
+
def relative_source_path
|
|
235
|
+
source_path.relative_path_from(app.root)
|
|
236
|
+
end
|
|
237
|
+
|
|
226
238
|
# Returns the slice's configured inflector.
|
|
227
239
|
#
|
|
228
240
|
# Unless explicitly re-configured for the slice, this will be the app's inflector.
|
|
@@ -729,7 +741,9 @@ module Hanami
|
|
|
729
741
|
# @api public
|
|
730
742
|
# @since 2.0.0
|
|
731
743
|
def routes
|
|
732
|
-
@routes
|
|
744
|
+
return @routes if instance_variable_defined?(:@routes)
|
|
745
|
+
|
|
746
|
+
@routes = load_routes
|
|
733
747
|
end
|
|
734
748
|
|
|
735
749
|
# Returns the slice's router, if or nil if no routes are defined.
|
|
@@ -925,7 +939,7 @@ module Hanami
|
|
|
925
939
|
# Check here for the `routes` definition only, not `router` itself, because the
|
|
926
940
|
# `router` requires the slice to be prepared before it can be loaded, and at this
|
|
927
941
|
# point we're still in the process of preparing.
|
|
928
|
-
if routes
|
|
942
|
+
if routes?
|
|
929
943
|
require_relative "providers/routes"
|
|
930
944
|
register_provider(:routes, source: Providers::Routes)
|
|
931
945
|
end
|
|
@@ -954,9 +968,10 @@ module Hanami
|
|
|
954
968
|
end
|
|
955
969
|
|
|
956
970
|
def prepare_autoloader
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
#
|
|
971
|
+
autoloader.tag = "hanami.slices.#{slice_name.to_s}"
|
|
972
|
+
|
|
973
|
+
# Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
|
|
974
|
+
# This method adds other dirs that are not otherwise configured as component dirs.
|
|
960
975
|
|
|
961
976
|
# Everything in the slice root can be autoloaded except `config/` and `slices/`,
|
|
962
977
|
# which are framework-managed directories
|
|
@@ -977,11 +992,19 @@ module Hanami
|
|
|
977
992
|
slices.freeze
|
|
978
993
|
end
|
|
979
994
|
|
|
995
|
+
def routes?
|
|
996
|
+
return false unless Hanami.bundled?("hanami-router")
|
|
997
|
+
|
|
998
|
+
return true if namespace.const_defined?(ROUTES_CLASS_NAME)
|
|
999
|
+
|
|
1000
|
+
root.join("#{ROUTES_PATH}#{RB_EXT}").file?
|
|
1001
|
+
end
|
|
1002
|
+
|
|
980
1003
|
def load_routes
|
|
981
1004
|
return false unless Hanami.bundled?("hanami-router")
|
|
982
1005
|
|
|
983
1006
|
if root.directory?
|
|
984
|
-
routes_require_path =
|
|
1007
|
+
routes_require_path = root.join(ROUTES_PATH).to_s
|
|
985
1008
|
|
|
986
1009
|
begin
|
|
987
1010
|
require_relative "./routes"
|
|
@@ -1050,6 +1073,11 @@ module Hanami
|
|
|
1050
1073
|
if config.actions.sessions.enabled?
|
|
1051
1074
|
use(*config.actions.sessions.middleware)
|
|
1052
1075
|
end
|
|
1076
|
+
|
|
1077
|
+
if config.actions.content_security_policy && # rubocop:disable Style/SafeNavigation
|
|
1078
|
+
config.actions.content_security_policy.nonce?
|
|
1079
|
+
use(*config.actions.content_security_policy.middleware)
|
|
1080
|
+
end
|
|
1053
1081
|
end
|
|
1054
1082
|
|
|
1055
1083
|
if Hanami.bundled?("hanami-assets") && config.assets.serve
|
data/lib/hanami/version.rb
CHANGED
data/lib/hanami.rb
CHANGED
|
@@ -138,7 +138,15 @@ module Hanami
|
|
|
138
138
|
end
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
# Returns the Hanami app environment as
|
|
141
|
+
# Returns the Hanami app environment as determined from the environment.
|
|
142
|
+
#
|
|
143
|
+
# Checks the following environment variables in order:
|
|
144
|
+
#
|
|
145
|
+
# - `HANAMI_ENV`
|
|
146
|
+
# - `APP_ENV`
|
|
147
|
+
# - `RACK_ENV`
|
|
148
|
+
#
|
|
149
|
+
# Defaults to `:development` if no environment variable is set.
|
|
142
150
|
#
|
|
143
151
|
# @example
|
|
144
152
|
# Hanami.env # => :development
|
|
@@ -148,7 +156,7 @@ module Hanami
|
|
|
148
156
|
# @api public
|
|
149
157
|
# @since 2.0.0
|
|
150
158
|
def self.env(e: ENV)
|
|
151
|
-
e
|
|
159
|
+
(e["HANAMI_ENV"] || e["APP_ENV"] || e["RACK_ENV"] || :development).to_sym
|
|
152
160
|
end
|
|
153
161
|
|
|
154
162
|
# Returns true if {.env} matches any of the given names
|
|
@@ -20,7 +20,7 @@ RSpec.describe "App action / Format config", :app_integration do
|
|
|
20
20
|
class App < Hanami::App
|
|
21
21
|
config.logger.stream = StringIO.new
|
|
22
22
|
|
|
23
|
-
config.actions.
|
|
23
|
+
config.actions.formats.accept :json
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
RUBY
|
|
@@ -76,7 +76,10 @@ RSpec.describe "App action / Format config", :app_integration do
|
|
|
76
76
|
class App < Hanami::App
|
|
77
77
|
config.logger.stream = StringIO.new
|
|
78
78
|
|
|
79
|
-
config.actions.formats.
|
|
79
|
+
config.actions.formats.register :json, "application/json",
|
|
80
|
+
accept_types: ["application/json", "application/json+scim"],
|
|
81
|
+
content_types: ["application/json", "application/json+scim"]
|
|
82
|
+
config.actions.formats.accept :json
|
|
80
83
|
end
|
|
81
84
|
end
|
|
82
85
|
RUBY
|
|
@@ -141,7 +144,7 @@ RSpec.describe "App action / Format config", :app_integration do
|
|
|
141
144
|
class App < Hanami::App
|
|
142
145
|
config.logger.stream = StringIO.new
|
|
143
146
|
|
|
144
|
-
config.actions.
|
|
147
|
+
config.actions.formats.accept :json
|
|
145
148
|
config.middleware.use :body_parser, [json: "application/json+custom"]
|
|
146
149
|
end
|
|
147
150
|
end
|
|
@@ -34,25 +34,25 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
34
34
|
it "applies default actions config from the app", :aggregate_failures do
|
|
35
35
|
prepare_app
|
|
36
36
|
|
|
37
|
-
expect(TestApp::Action.config.formats.
|
|
37
|
+
expect(TestApp::Action.config.formats.accepted).to eq []
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
it "applies actions config from the app" do
|
|
41
|
-
Hanami.app.config.actions.
|
|
41
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
42
42
|
|
|
43
43
|
prepare_app
|
|
44
44
|
|
|
45
|
-
expect(TestApp::Action.config.formats.
|
|
45
|
+
expect(TestApp::Action.config.formats.accepted).to eq [:json]
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
it "does not override config in the base class" do
|
|
49
|
-
Hanami.app.config.actions.
|
|
49
|
+
Hanami.app.config.actions.formats.accepted = [:csv]
|
|
50
50
|
|
|
51
51
|
prepare_app
|
|
52
52
|
|
|
53
|
-
TestApp::Action.config.
|
|
53
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
54
54
|
|
|
55
|
-
expect(TestApp::Action.config.formats.
|
|
55
|
+
expect(TestApp::Action.config.formats.accepted).to eq [:json]
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -75,23 +75,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
75
75
|
it "applies default actions config from the app", :aggregate_failures do
|
|
76
76
|
prepare_app
|
|
77
77
|
|
|
78
|
-
expect(TestApp::Actions::Articles::Index.config.formats.
|
|
78
|
+
expect(TestApp::Actions::Articles::Index.config.formats.accepted).to eq []
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
it "applies actions config from the app" do
|
|
82
|
-
Hanami.app.config.actions.
|
|
82
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
83
83
|
|
|
84
84
|
prepare_app
|
|
85
85
|
|
|
86
|
-
expect(TestApp::Actions::Articles::Index.config.formats.
|
|
86
|
+
expect(TestApp::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
it "applies config from the base class" do
|
|
90
90
|
prepare_app
|
|
91
91
|
|
|
92
|
-
TestApp::Action.config.
|
|
92
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
93
93
|
|
|
94
|
-
expect(TestApp::Actions::Articles::Index.config.formats.
|
|
94
|
+
expect(TestApp::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
@@ -114,23 +114,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
114
114
|
it "applies default actions config from the app", :aggregate_failures do
|
|
115
115
|
prepare_app
|
|
116
116
|
|
|
117
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
117
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq []
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
it "applies actions config from the app" do
|
|
121
|
-
Hanami.app.config.actions.
|
|
121
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
122
122
|
|
|
123
123
|
prepare_app
|
|
124
124
|
|
|
125
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
125
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
it "applies config from the base class" do
|
|
129
129
|
prepare_app
|
|
130
130
|
|
|
131
|
-
TestApp::Action.config.
|
|
131
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
132
132
|
|
|
133
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
133
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
end
|
|
@@ -151,23 +151,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
151
151
|
it "applies default actions config from the app", :aggregate_failures do
|
|
152
152
|
prepare_app
|
|
153
153
|
|
|
154
|
-
expect(Admin::Action.config.formats.
|
|
154
|
+
expect(Admin::Action.config.formats.accepted).to eq []
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
it "applies actions config from the app" do
|
|
158
|
-
Hanami.app.config.actions.
|
|
158
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
159
159
|
|
|
160
160
|
prepare_app
|
|
161
161
|
|
|
162
|
-
expect(Admin::Action.config.formats.
|
|
162
|
+
expect(Admin::Action.config.formats.accepted).to eq [:json]
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
it "applies config from the app base class" do
|
|
166
166
|
prepare_app
|
|
167
167
|
|
|
168
|
-
TestApp::Action.config.
|
|
168
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
169
169
|
|
|
170
|
-
expect(Admin::Action.config.formats.
|
|
170
|
+
expect(Admin::Action.config.formats.accepted).to eq [:json]
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
context "slice actions config present" do
|
|
@@ -186,24 +186,24 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
186
186
|
it "applies actions config from the slice" do
|
|
187
187
|
prepare_app
|
|
188
188
|
|
|
189
|
-
expect(Admin::Action.config.formats.
|
|
189
|
+
expect(Admin::Action.config.formats.accepted).to eq [:csv]
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
it "prefers actions config from the slice over config from the app-level base class" do
|
|
193
193
|
prepare_app
|
|
194
194
|
|
|
195
|
-
TestApp::Action.config.
|
|
195
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
196
196
|
|
|
197
|
-
expect(Admin::Action.config.formats.
|
|
197
|
+
expect(Admin::Action.config.formats.accepted).to eq [:csv]
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
it "prefers config from the base class over actions config from the slice" do
|
|
201
201
|
prepare_app
|
|
202
202
|
|
|
203
|
-
TestApp::Action.config.
|
|
204
|
-
Admin::Action.config.
|
|
203
|
+
TestApp::Action.config.formats.accepted = [:csv]
|
|
204
|
+
Admin::Action.config.formats.accepted = [:json]
|
|
205
205
|
|
|
206
|
-
expect(Admin::Action.config.formats.
|
|
206
|
+
expect(Admin::Action.config.formats.accepted).to eq [:json]
|
|
207
207
|
end
|
|
208
208
|
end
|
|
209
209
|
end
|
|
@@ -227,15 +227,15 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
227
227
|
it "applies default actions config from the app", :aggregate_failures do
|
|
228
228
|
prepare_app
|
|
229
229
|
|
|
230
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
230
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq []
|
|
231
231
|
end
|
|
232
232
|
|
|
233
233
|
it "applies actions config from the app" do
|
|
234
|
-
Hanami.app.config.actions.
|
|
234
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
235
235
|
|
|
236
236
|
prepare_app
|
|
237
237
|
|
|
238
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
238
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
239
239
|
end
|
|
240
240
|
|
|
241
241
|
it "applies actions config from the slice" do
|
|
@@ -243,7 +243,7 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
243
243
|
write "config/slices/admin.rb", <<~'RUBY'
|
|
244
244
|
module Admin
|
|
245
245
|
class Slice < Hanami::Slice
|
|
246
|
-
config.actions.
|
|
246
|
+
config.actions.formats.accepted = [:json]
|
|
247
247
|
end
|
|
248
248
|
end
|
|
249
249
|
RUBY
|
|
@@ -251,15 +251,15 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
251
251
|
|
|
252
252
|
prepare_app
|
|
253
253
|
|
|
254
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
254
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
255
255
|
end
|
|
256
256
|
|
|
257
257
|
it "applies config from the slice base class" do
|
|
258
258
|
prepare_app
|
|
259
259
|
|
|
260
|
-
Admin::Action.config.
|
|
260
|
+
Admin::Action.config.formats.accepted = [:json]
|
|
261
261
|
|
|
262
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
262
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
263
263
|
end
|
|
264
264
|
|
|
265
265
|
it "prefers config from the slice base class over actions config from the slice" do
|
|
@@ -275,9 +275,9 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
275
275
|
|
|
276
276
|
prepare_app
|
|
277
277
|
|
|
278
|
-
Admin::Action.config.
|
|
278
|
+
Admin::Action.config.formats.accepted = [:json]
|
|
279
279
|
|
|
280
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
280
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
281
281
|
end
|
|
282
282
|
end
|
|
283
283
|
end
|
|
@@ -123,7 +123,6 @@ RSpec.describe "Cross-slice assets via helpers", :app_integration do
|
|
|
123
123
|
compile_assets!
|
|
124
124
|
|
|
125
125
|
output = Admin::Slice["views.posts.show"].call.to_s
|
|
126
|
-
|
|
127
126
|
expect(output).to match(%r{<link href="/assets/app-[A-Z0-9]{8}.css" type="text/css" rel="stylesheet">})
|
|
128
127
|
expect(output).to match(%r{<script src="/assets/app-[A-Z0-9]{8}.js" type="text/javascript"></script>})
|
|
129
128
|
end
|