hanami 2.0.0 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ff0a8cf3ce1df8c90acbb5feb8352cc0f05d0981ad9b08ac4f6f62351e8d775
4
- data.tar.gz: ec3ca71f07e1f4120fb6c4792163a2ffbf926de2160264a294563b6d2a6c77d1
3
+ metadata.gz: '099623ad9ce0088b28fc658340d85094d493f27e4320df725d4dfc062ee4939c'
4
+ data.tar.gz: 759492d76fb078f025d00e57d8c81cc5840ca769e5a20a0e50d20a0221c9a599
5
5
  SHA512:
6
- metadata.gz: ce1c3e798ba14c6ec3778beb25b47feaf87bc32dd995797836cdb8a7af9b8e13a6d401e7005f8ec78cf3da4ec5cc5be086f0ff2763eb2082939900ede5f4bae3
7
- data.tar.gz: eb4f586e78f1afa3f674ca3e9f7499d7699e4d0c432aef744f98d788a8a77398f11ee27bd9619ed98463bc8065a28d35e9cb8e73b41b18c4044aaab7c9549507
6
+ metadata.gz: fa25a5f37d22c1faf03906914f1c0a4f5d119ae39088df6878d75ef5c830b7be7632b2b633aa915619ff44990784016da4df666b5343b56166cb31a1f613a4ea
7
+ data.tar.gz: ff70c18b250591af4c2fbaf32532a7ca5cb00adbc17ab87a03e0bb0dddd0ca5a19a7440aa63d0abc9c1570d9784027a4981d2d097632852e7b69f150ebd56328
data/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  The web, with simplicity.
4
4
 
5
+ ## v2.0.2 - 2022-12-25
6
+
7
+ ### Added
8
+
9
+ - [Luca Guidi] Official support for Ruby 3.2
10
+
11
+ ### Fixed
12
+
13
+ - [Luca Guidi] Content Security Policy: remove deprecated `plugin-types`
14
+
15
+ ## v2.0.1 - 2022-12-06
16
+
17
+ ### Fixed
18
+
19
+ - [Luca Guidi] Ensure `Content-Security-Policy` HTTP response header to be returned as a single line
20
+ - [Tim Riley] Ensure Rack events are on internal notifications system
21
+
5
22
  ## v2.0.0 - 2022-11-22
6
23
 
7
24
  ### Added
data/lib/hanami/app.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "constants"
4
+ require_relative "env"
4
5
 
5
6
  module Hanami
6
7
  # The Hanami app is a singular slice tasked with managing the core components of the app and
@@ -28,8 +29,7 @@ module Hanami
28
29
  @_mutex.synchronize do
29
30
  subclass.class_eval do
30
31
  @config = Hanami::Config.new(app_name: slice_name, env: Hanami.env)
31
-
32
- load_dotenv
32
+ Hanami::Env.load
33
33
  end
34
34
  end
35
35
  end
@@ -97,32 +97,6 @@ module Hanami
97
97
 
98
98
  private
99
99
 
100
- # Uses [dotenv](https://github.com/bkeepers/dotenv) (if available) to populate `ENV` from
101
- # various `.env` files.
102
- #
103
- # For a given `HANAMI_ENV` environment, the `.env` files are looked up in the following order:
104
- #
105
- # - .env.{environment}.local
106
- # - .env.local (unless the environment is `test`)
107
- # - .env.{environment}
108
- # - .env
109
- #
110
- # If dotenv is unavailable, the method exits and does nothing.
111
- def load_dotenv
112
- return unless Hanami.bundled?("dotenv")
113
-
114
- hanami_env = Hanami.env
115
- dotenv_files = [
116
- ".env.#{hanami_env}.local",
117
- (".env.local" unless hanami_env == :test),
118
- ".env.#{hanami_env}",
119
- ".env"
120
- ].compact
121
-
122
- require "dotenv"
123
- Dotenv.load(*dotenv_files)
124
- end
125
-
126
100
  def prepare_all
127
101
  prepare_load_path
128
102
 
@@ -22,7 +22,6 @@ module Hanami
22
22
  img_src: "'self' https: data:",
23
23
  media_src: "'self'",
24
24
  object_src: "'none'",
25
- plugin_types: "application/pdf",
26
25
  script_src: "'self'",
27
26
  style_src: "'self' 'unsafe-inline' https:"
28
27
  }
@@ -102,7 +101,7 @@ module Hanami
102
101
  def to_s
103
102
  @policy.map do |key, value|
104
103
  "#{dasherize(key)} #{value}"
105
- end.join(";\n")
104
+ end.join(";")
106
105
  end
107
106
 
108
107
  private
data/lib/hanami/env.rb ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Env
5
+ # @since 2.0.1
6
+ # @api private
7
+ @_loaded = false
8
+
9
+ # Uses [dotenv](https://github.com/bkeepers/dotenv) (if available) to populate `ENV` from
10
+ # various `.env` files.
11
+ #
12
+ # For a given `HANAMI_ENV` environment, the `.env` files are looked up in the following order:
13
+ #
14
+ # - .env.{environment}.local
15
+ # - .env.local (unless the environment is `test`)
16
+ # - .env.{environment}
17
+ # - .env
18
+ #
19
+ # If dotenv is unavailable, the method exits and does nothing.
20
+ #
21
+ # @since 2.0.1
22
+ # @api private
23
+ def self.load(env = Hanami.env)
24
+ return unless Hanami.bundled?("dotenv")
25
+ return if loaded?
26
+
27
+ dotenv_files = [
28
+ ".env.#{env}.local",
29
+ (".env.local" unless env == :test),
30
+ ".env.#{env}",
31
+ ".env"
32
+ ].compact
33
+
34
+ require "dotenv"
35
+ Dotenv.load(*dotenv_files)
36
+
37
+ loaded!
38
+ end
39
+
40
+ # @since 2.0.1
41
+ # @api private
42
+ def self.loaded?
43
+ @_loaded
44
+ end
45
+
46
+ # @since 2.0.1
47
+ # @api private
48
+ def self.loaded!
49
+ @_loaded = true
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ # @since 2.0.1
5
+ # @api private
6
+ module Port
7
+ # @since 2.0.1
8
+ # @api private
9
+ DEFAULT = 2300
10
+
11
+ # @since 2.0.1
12
+ # @api private
13
+ ENV_VAR = "HANAMI_PORT"
14
+
15
+ # @since 2.0.1
16
+ # @api private
17
+ def self.call(value, env = ENV.fetch(ENV_VAR, nil))
18
+ return Integer(value) if !value.nil? && !default?(value)
19
+ return Integer(env) unless env.nil?
20
+ return Integer(value) unless value.nil?
21
+
22
+ DEFAULT
23
+ end
24
+
25
+ # @since 2.0.1
26
+ # @api private
27
+ def self.call!(value)
28
+ return if default?(value)
29
+
30
+ ENV[ENV_VAR] = value.to_s
31
+ end
32
+
33
+ # @since 2.0.1
34
+ # @api private
35
+ def self.default?(value)
36
+ value.to_i == DEFAULT
37
+ end
38
+
39
+ class << self
40
+ # @since 2.0.1
41
+ # @api private
42
+ alias_method :[], :call
43
+ end
44
+ end
45
+ end
@@ -15,20 +15,27 @@ module Hanami
15
15
  class Rack < Dry::System::Provider::Source
16
16
  # @api private
17
17
  def prepare
18
- require "dry/monitor"
19
- require "hanami/web/rack_logger"
20
-
21
18
  Dry::Monitor.load_extensions(:rack)
19
+
20
+ # Explicitly register the Rack middleware events on our notifications bus. The Dry::Monitor
21
+ # rack extension (activated above) does register these globally, but if the notifications
22
+ # bus has been used before this provider loads, then it will have created its own separate
23
+ # locally copy of all registered events as of that moment in time, which will not included
24
+ # the Rack events globally reigstered above.
25
+ notifications = target["notifications"]
26
+ notifications.register_event(Dry::Monitor::Rack::Middleware::REQUEST_START)
27
+ notifications.register_event(Dry::Monitor::Rack::Middleware::REQUEST_STOP)
28
+ notifications.register_event(Dry::Monitor::Rack::Middleware::REQUEST_ERROR)
22
29
  end
23
30
 
24
31
  # @api private
25
32
  def start
26
33
  target.start :logger
27
34
 
28
- notifications = target[:notifications]
29
-
30
- clock = Dry::Monitor::Clock.new(unit: :microsecond)
31
- monitor_middleware = Dry::Monitor::Rack::Middleware.new(notifications, clock: clock)
35
+ monitor_middleware = Dry::Monitor::Rack::Middleware.new(
36
+ target["notifications"],
37
+ clock: Dry::Monitor::Clock.new(unit: :microsecond)
38
+ )
32
39
 
33
40
  rack_logger = Hanami::Web::RackLogger.new(target[:logger], env: target.env)
34
41
  rack_logger.attach(monitor_middleware)
@@ -7,7 +7,7 @@ module Hanami
7
7
  # @api private
8
8
  module Version
9
9
  # @api public
10
- VERSION = "2.0.0"
10
+ VERSION = "2.0.2"
11
11
 
12
12
  # @since 0.9.0
13
13
  # @api private
@@ -5,6 +5,7 @@
5
5
  RSpec.describe "Dotenv loading", :app_integration do
6
6
  before do
7
7
  @orig_env = ENV.to_h
8
+ allow(Hanami::Env).to receive(:loaded?).and_return(false)
8
9
  end
9
10
 
10
11
  after do
@@ -87,7 +87,7 @@ RSpec.describe "Hanami web app", :app_integration do
87
87
  end
88
88
  RUBY
89
89
 
90
- require "hanami/boot"
90
+ require "hanami/prepare"
91
91
 
92
92
  expect(Hanami.app["rack.monitor"]).to be_instance_of(Dry::Monitor::Rack::Middleware)
93
93
 
@@ -114,7 +114,63 @@ RSpec.describe "Hanami web app", :app_integration do
114
114
  end
115
115
  end
116
116
 
117
- describe "Request logging when using a slice" do
117
+ specify "Logging via the rack monitor even when notifications bus has already been used" do
118
+ dir = Dir.mktmpdir
119
+
120
+ with_tmp_directory(dir) do
121
+ write "config/app.rb", <<~RUBY
122
+ require "hanami"
123
+
124
+ module TestApp
125
+ class App < Hanami::App
126
+ config.actions.format :json
127
+ config.logger.options = {colorize: true}
128
+ config.logger.stream = config.root.join("test.log")
129
+ end
130
+ end
131
+ RUBY
132
+
133
+ write "config/routes.rb", <<~RUBY
134
+ module TestApp
135
+ class Routes < Hanami::Routes
136
+ post "/users", to: "users.create"
137
+ end
138
+ end
139
+ RUBY
140
+
141
+ write "app/actions/users/create.rb", <<~RUBY
142
+ module TestApp
143
+ module Actions
144
+ module Users
145
+ class Create < Hanami::Action
146
+ def handle(req, resp)
147
+ resp.body = req.params.to_h.keys
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ RUBY
154
+
155
+ require "hanami/prepare"
156
+
157
+ # Simulate any component interacting with the notifications bus such that it creates its
158
+ # internal bus with a duplicate copy of all currently registered events. This means that the
159
+ # class-level Dry::Monitor::Notification events implicitly registered by the
160
+ # Dry::Monitor::Rack::Middleware activated via the rack provider are ignored, unless our
161
+ # provider explicitly re-registers them on _instance_ of the notifications bus.
162
+ #
163
+ # See Hanami::Providers::Rack for more detail.
164
+ Hanami.app["notifications"].instrument(:sql)
165
+
166
+ logs = -> { Pathname(dir).join("test.log").realpath.read }
167
+
168
+ post "/users", JSON.generate(name: "jane", password: "secret"), {"CONTENT_TYPE" => "application/json"}
169
+ expect(logs.()).to match %r{POST 200 \d+(µs|ms) 127.0.0.1 /}
170
+ end
171
+ end
172
+
173
+ describe "Request logging when using slice router" do
118
174
  let(:app) { Main::Slice.rack_app }
119
175
 
120
176
  specify "Has rack monitor preconfigured with default request logging (when used via a slice)" do
@@ -139,7 +195,7 @@ RSpec.describe "Hanami web app", :app_integration do
139
195
  end
140
196
  RUBY
141
197
 
142
- require "hanami/boot"
198
+ require "hanami/prepare"
143
199
 
144
200
  get "/"
145
201
 
@@ -3,6 +3,7 @@
3
3
  RSpec.describe "Settings / Access within slice class bodies", :app_integration do
4
4
  before do
5
5
  @env = ENV.to_h
6
+ allow(Hanami::Env).to receive(:loaded?).and_return(false)
6
7
  end
7
8
 
8
9
  after do
@@ -3,6 +3,7 @@
3
3
  RSpec.describe "Settings / Access to constants", :app_integration do
4
4
  before do
5
5
  @env = ENV.to_h
6
+ allow(Hanami::Env).to receive(:loaded?).and_return(false)
6
7
  end
7
8
 
8
9
  after do
@@ -13,21 +13,20 @@ RSpec.describe Hanami::Config::Actions, "#content_security_policy" do
13
13
  expect(content_security_policy[:base_uri]).to eq("'self'")
14
14
 
15
15
  expected = [
16
- %(base-uri 'self';),
17
- %(child-src 'self';),
18
- %(connect-src 'self';),
19
- %(default-src 'none';),
20
- %(font-src 'self';),
21
- %(form-action 'self';),
22
- %(frame-ancestors 'self';),
23
- %(frame-src 'self';),
24
- %(img-src 'self' https: data:;),
25
- %(media-src 'self';),
26
- %(object-src 'none';),
27
- %(plugin-types application/pdf;),
28
- %(script-src 'self';),
16
+ %(base-uri 'self'),
17
+ %(child-src 'self'),
18
+ %(connect-src 'self'),
19
+ %(default-src 'none'),
20
+ %(font-src 'self'),
21
+ %(form-action 'self'),
22
+ %(frame-ancestors 'self'),
23
+ %(frame-src 'self'),
24
+ %(img-src 'self' https: data:),
25
+ %(media-src 'self'),
26
+ %(object-src 'none'),
27
+ %(script-src 'self'),
29
28
  %(style-src 'self' 'unsafe-inline' https:)
30
- ].join("\n")
29
+ ].join(";")
31
30
 
32
31
  expect(content_security_policy.to_s).to eq(expected)
33
32
  end
@@ -62,10 +61,10 @@ RSpec.describe Hanami::Config::Actions, "#content_security_policy" do
62
61
  end
63
62
 
64
63
  it "nullifies value" do
65
- content_security_policy[:plugin_types] = nil
64
+ content_security_policy[:object_src] = nil
66
65
 
67
- expect(content_security_policy[:plugin_types]).to be(nil)
68
- expect(content_security_policy.to_s).to match("plugin-types ;")
66
+ expect(content_security_policy[:object_src]).to be(nil)
67
+ expect(content_security_policy.to_s).to match("object-src ;")
69
68
  end
70
69
 
71
70
  it "deletes key" do
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/port"
4
+
5
+ RSpec.describe Hanami::Port do
6
+ context "Hanami::Port::DEFAULT" do
7
+ it "returns default value" do
8
+ expect(Hanami::Port::DEFAULT).to eq(2300)
9
+ end
10
+ end
11
+
12
+ context "Hanami::Port::ENV_VAR" do
13
+ it "returns default value" do
14
+ expect(Hanami::Port::ENV_VAR).to eq("HANAMI_PORT")
15
+ end
16
+ end
17
+
18
+ context ".call" do
19
+ let(:value) { nil }
20
+ let(:env) { nil }
21
+
22
+ it "is aliased as .[]" do
23
+ expect(described_class[value, env]).to be(2300)
24
+ end
25
+
26
+ context "when ENV var is nil" do
27
+ context "and value is nil" do
28
+ it "returns default value" do
29
+ expect(described_class.call(value, env)).to be(2300)
30
+ end
31
+ end
32
+
33
+ context "and value is not nil" do
34
+ let(:value) { 18_000 }
35
+
36
+ it "returns given value" do
37
+ expect(described_class.call(value, env)).to be(value)
38
+ end
39
+
40
+ context "and value is default" do
41
+ let(:value) { 2300 }
42
+
43
+ it "returns given value" do
44
+ expect(described_class.call(value, env)).to be(value)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ context "when ENV var not nil" do
51
+ let(:env) { 9000 }
52
+
53
+ context "and value is nil" do
54
+ it "returns env value" do
55
+ expect(described_class.call(value, env)).to be(env)
56
+ end
57
+ end
58
+
59
+ context "and value is not nil" do
60
+ let(:value) { 18_000 }
61
+
62
+ it "returns given value" do
63
+ expect(described_class.call(value, env)).to be(value)
64
+ end
65
+
66
+ context "and value is default" do
67
+ let(:value) { 2300 }
68
+
69
+ it "returns env value" do
70
+ expect(described_class.call(value, env)).to be(env)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ context ".call!" do
78
+ before { ENV.delete("HANAMI_PORT") }
79
+ let(:value) { 2300 }
80
+
81
+ context "when given value is default" do
82
+ it "doesn't set env var" do
83
+ described_class.call!(value)
84
+
85
+ expect(ENV.key?("HANAMI_PORT")).to be(false)
86
+ end
87
+ end
88
+
89
+ context "when given value isn't default" do
90
+ let(:value) { 9000 }
91
+
92
+ it "set env var" do
93
+ described_class.call!(value)
94
+
95
+ expect(ENV.fetch("HANAMI_PORT")).to eq(value.to_s)
96
+ end
97
+ end
98
+ end
99
+
100
+ context ".default?" do
101
+ context "when given value is default" do
102
+ let(:value) { 2300 }
103
+
104
+ it "returns true" do
105
+ expect(described_class.default?(value)).to be(true)
106
+ end
107
+ end
108
+
109
+ context "when given value isn't default" do
110
+ let(:value) { 9000 }
111
+
112
+ it "returns false" do
113
+ expect(described_class.default?(value)).to be(false)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  RSpec.describe "Hanami::VERSION" do
4
4
  it "returns current version" do
5
- expect(Hanami::VERSION).to eq("2.0.0")
5
+ expect(Hanami::VERSION).to eq("2.0.2")
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-22 00:00:00.000000000 Z
11
+ date: 2022-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -268,6 +268,7 @@ files:
268
268
  - lib/hanami/config/router.rb
269
269
  - lib/hanami/config/views.rb
270
270
  - lib/hanami/constants.rb
271
+ - lib/hanami/env.rb
271
272
  - lib/hanami/errors.rb
272
273
  - lib/hanami/extensions.rb
273
274
  - lib/hanami/extensions/action.rb
@@ -276,6 +277,7 @@ files:
276
277
  - lib/hanami/extensions/view/context.rb
277
278
  - lib/hanami/extensions/view/slice_configured_context.rb
278
279
  - lib/hanami/extensions/view/slice_configured_view.rb
280
+ - lib/hanami/port.rb
279
281
  - lib/hanami/prepare.rb
280
282
  - lib/hanami/providers/inflector.rb
281
283
  - lib/hanami/providers/logger.rb
@@ -376,6 +378,7 @@ files:
376
378
  - spec/unit/hanami/config/slices_spec.rb
377
379
  - spec/unit/hanami/config/views_spec.rb
378
380
  - spec/unit/hanami/env_spec.rb
381
+ - spec/unit/hanami/port_spec.rb
379
382
  - spec/unit/hanami/settings/env_store_spec.rb
380
383
  - spec/unit/hanami/settings_spec.rb
381
384
  - spec/unit/hanami/slice_configurable_spec.rb
@@ -404,7 +407,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
404
407
  - !ruby/object:Gem::Version
405
408
  version: '0'
406
409
  requirements: []
407
- rubygems_version: 3.3.7
410
+ rubygems_version: 3.4.1
408
411
  signing_key:
409
412
  specification_version: 4
410
413
  summary: The web, with simplicity
@@ -488,6 +491,7 @@ test_files:
488
491
  - spec/unit/hanami/config/slices_spec.rb
489
492
  - spec/unit/hanami/config/views_spec.rb
490
493
  - spec/unit/hanami/env_spec.rb
494
+ - spec/unit/hanami/port_spec.rb
491
495
  - spec/unit/hanami/settings/env_store_spec.rb
492
496
  - spec/unit/hanami/settings_spec.rb
493
497
  - spec/unit/hanami/slice_configurable_spec.rb