hanami 2.0.0 → 2.0.2

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 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